1
0
Fork 0

Hover and Go-To definition (in progress)

pull/357/head
Sara Itani 2017-03-05 20:22:13 -08:00
parent cdf8fc36e1
commit d3f2bebb40
6 changed files with 394 additions and 258 deletions

View File

@ -55,7 +55,7 @@ class TestClass implements TestInterface
* @param TestClass $testParameter Lorem sunt velit incididunt mollit * @param TestClass $testParameter Lorem sunt velit incididunt mollit
* @return TestClass * @return TestClass
*/ */
public function testMethod($testParameter): TestInterface public function testMethod($testParameter) : TestInterface
{ {
$this->testProperty = $testParameter; $this->testProperty = $testParameter;
} }

View File

@ -256,7 +256,11 @@ class PhpDocument
return $finder->node; return $finder->node;
} else { } else {
$offset = $position->toOffset($this->stmts->getFileContents()); $offset = $position->toOffset($this->stmts->getFileContents());
return $this->stmts->getDescendantNodeAtPosition($offset); $node = $this->stmts->getDescendantNodeAtPosition($offset);
if ($node->getStart() > $offset) {
return null;
}
return $node;
} }
} }

View File

@ -3,6 +3,7 @@
namespace LanguageServer\Protocol; namespace LanguageServer\Protocol;
use PhpParser\{Error, Node}; use PhpParser\{Error, Node};
use Microsoft\PhpParser as Tolerant;
/** /**
* A range in a text document expressed as (zero-based) start and end positions. * A range in a text document expressed as (zero-based) start and end positions.
@ -26,15 +27,24 @@ class Range
/** /**
* Returns the range the node spans * Returns the range the node spans
* *
* @param Node $node * @param Node | Tolerant\Node $node
* @return self * @return self
*/ */
public static function fromNode(Node $node) public static function fromNode($node)
{ {
if ($node instanceof Node) {
return new self( return new self(
new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1), new Position($node->getAttribute('startLine') - 1, $node->getAttribute('startColumn') - 1),
new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn')) new Position($node->getAttribute('endLine') - 1, $node->getAttribute('endColumn'))
); );
} else {
$range = Tolerant\PositionUtilities::getRangeFromPosition($node->getStart(), $node->getWidth(), $node->getFileContents());
return new self(
new Position($range->start->line, $range->start->character),
new Position($range->end->line, $range->end->character)
);
}
} }
/** /**

View File

@ -7,7 +7,7 @@ use LanguageServer\Protocol\TolerantSymbolInformation;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use phpDocumentor\Reflection\{ use phpDocumentor\Reflection\{
DocBlockFactory, Types, Type, Fqsen, TypeResolver DocBlock, DocBlockFactory, Types, Type, Fqsen, TypeResolver
}; };
use LanguageServer\Protocol\SymbolInformation; use LanguageServer\Protocol\SymbolInformation;
use LanguageServer\Index\ReadableIndex; use LanguageServer\Index\ReadableIndex;
@ -65,7 +65,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
$defLine, $defLine,
$node->getFullText(), $node->getFullText(),
$propertyDeclaration->propertyElements->getFullStart() - $defLineStart, $propertyDeclaration->propertyElements->getFullStart() - $defLineStart,
$propertyDeclaration->propertyElements->getWidth() $propertyDeclaration->propertyElements->getFullWidth()
); );
} elseif ( } elseif (
// ClassConstDeclaration or ConstDeclaration // const A = 1, B = 2; // ClassConstDeclaration or ConstDeclaration // const A = 1, B = 2;
@ -79,7 +79,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
$defLine, $defLine,
$node->getFullText(), $node->getFullText(),
$constDeclaration->constElements->getFullStart() - $defLineStart, $constDeclaration->constElements->getFullStart() - $defLineStart,
$constDeclaration->constElements->getWidth() $constDeclaration->constElements->getFullWidth()
); );
} }
@ -89,6 +89,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
$defLine = \strtok($defLine, "\n"); $defLine = \strtok($defLine, "\n");
$defLine = \strtok($defLine, "\r");
return $defLine; return $defLine;
} }
@ -115,11 +116,12 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
// For parameters, parse the documentation to get the parameter tag. // For parameters, parse the documentation to get the parameter tag.
if ($node instanceof Tolerant\Node\Parameter) { if ($node instanceof Tolerant\Node\Parameter) {
$functionLikeDeclaration = $this->getFunctionLikeDeclarationFromParameter($node); $functionLikeDeclaration = $this->getFunctionLikeDeclarationFromParameter($node);
$variableName = $node->variableName->getText($node->getFileContents()); $variableName = substr($node->variableName->getText($node->getFileContents()), 1);
$docBlock = $this->getDocBlock($functionLikeDeclaration); $docBlock = $this->getDocBlock($functionLikeDeclaration);
if ($docBlock !== null) { if ($docBlock !== null) {
$parameterDocBlockTag = $this->getDocBlockTagForParameter($docBlock, $variableName); $parameterDocBlockTag = $this->getDocBlockTagForParameter($docBlock, $variableName);
var_dump($parameterDocBlockTag);
return $parameterDocBlockTag !== null ? $parameterDocBlockTag->getDescription()->render() : null; return $parameterDocBlockTag !== null ? $parameterDocBlockTag->getDescription()->render() : null;
} }
@ -190,8 +192,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
} }
$def->symbolInformation = TolerantSymbolInformation::fromNode($node, $fqn); $def->symbolInformation = TolerantSymbolInformation::fromNode($node, $fqn);
// $def->type = $this->getTypeFromNode($node); //TODO $def->type = $this->getTypeFromNode($node); //TODO
$def->type = new Types\Mixed;
$def->declarationLine = $this->getDeclarationLineFromNode($node); $def->declarationLine = $this->getDeclarationLineFromNode($node);
$def->documentation = $this->getDocumentationFromNode($node); $def->documentation = $this->getDocumentationFromNode($node);
return $def; return $def;
@ -200,15 +201,15 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
/** /**
* Given any node, returns the Definition object of the symbol that is referenced * Given any node, returns the Definition object of the symbol that is referenced
* *
* @param Node $node Any reference node * @param Tolerant\Node $node Any reference node
* @return Definition|null * @return Definition|null
*/ */
public function resolveReferenceNodeToDefinition($node) public function resolveReferenceNodeToDefinition($node)
{ {
// Variables are not indexed globally, as they stay in the file scope anyway // Variables are not indexed globally, as they stay in the file scope anyway
if ($node instanceof Node\Expr\Variable) { if ($node instanceof Tolerant\Node\Expression\Variable) {
// Resolve $this // Resolve $this
if ($node->name === 'this' && $fqn = $this->getContainingClassFqn($node)) { if ($node->getName() === 'this' && $fqn = $this->getContainingClassFqn($node)) {
return $this->index->getDefinition($fqn, false); return $this->index->getDefinition($fqn, false);
} }
// Resolve the variable to a definition node (assignment, param or closure use) // Resolve the variable to a definition node (assignment, param or closure use)
@ -226,8 +227,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
// 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'); $globalFallback = $this->isConstantFetch($node) || $node->getFirstAncestor(Tolerant\Node\Expression\CallExpression::class) !== null;
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
// Return the Definition object from the index index // Return the Definition object from the index index
return $this->index->getDefinition($fqn, $globalFallback); return $this->index->getDefinition($fqn, $globalFallback);
} }
@ -241,39 +241,63 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
*/ */
public function resolveReferenceNodeToFqn($node) public function resolveReferenceNodeToFqn($node)
{ {
$parent = $node->getAttribute('parentNode'); // TODO all name tokens should be a part of a node
$parent = $node->getParent();
if ( if ($node instanceof Tolerant\Node\QualifiedName) {
$node instanceof Node\Name && (
$parent instanceof Node\Stmt\ClassLike
|| $parent instanceof Node\Param
|| $parent instanceof Node\FunctionLike
|| $parent instanceof Node\Stmt\GroupUse
|| $parent instanceof Node\Expr\New_
|| $parent instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\ClassConstFetch
|| $parent instanceof Node\Expr\StaticPropertyFetch
|| $parent instanceof Node\Expr\Instanceof_
)
) {
// For extends, implements, type hints and classes of classes of static calls use the name directly // For extends, implements, type hints and classes of classes of static calls use the name directly
$name = (string)$node; $name = (string)$node->getResolvedName() ?? $node->getText();
// Only the name node should be considered a reference, not the UseUse node itself if (($useClause = $node->getFirstAncestor(Tolerant\Node\NamespaceUseGroupClause::class, Tolerant\Node\Statement\NamespaceUseDeclaration::class)) !== null) {
} else if ($parent instanceof Node\Stmt\UseUse) { if ($useClause instanceof Tolerant\Node\NamespaceUseGroupClause) {
$name = (string)$parent->name; $prefix = $useClause->parent->parent->namespaceName;
$grandParent = $parent->getAttribute('parentNode'); $prefix = $prefix === null ? "" : $prefix->getText();
if ($grandParent instanceof Node\Stmt\GroupUse) {
$name = $grandParent->prefix . '\\' . $name; $name = $prefix . "\\" . $name;
} else if ($grandParent instanceof Node\Stmt\Use_ && $grandParent->type === Node\Stmt\Use_::TYPE_FUNCTION) {
$name .= '()'; if ($useClause->functionOrConst === null) {
$useClause = $node->getFirstAncestor(Tolerant\Node\Statement\NamespaceUseDeclaration::class);
} }
} else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) { }
if ($node->name instanceof Node\Expr) {
if ($useClause->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) {
$name .= "()";
}
}
return $name;
}
/*elseif ($node instanceof Tolerant\Node\Expression\CallExpression || ($node = $node->getFirstAncestor(Tolerant\Node\Expression\CallExpression::class)) !== null) {
if ($node->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression) {
$qualifier = $node->callableExpression->scopeResolutionQualifier;
if ($qualifier instanceof Tolerant\Token) {
// resolve this/self/parent
} elseif ($qualifier instanceof Tolerant\Node\QualifiedName) {
$name = $qualifier->getResolvedName() ?? $qualifier->getNamespacedName();
$name .= "::";
$memberName = $node->callableExpression->memberName;
if ($memberName instanceof Tolerant\Token) {
$name .= $memberName->getText($node->getFileContents());
} elseif ($memberName instanceof Tolerant\Node\Expression\Variable) {
$name .= $memberName->getText();
} else {
return null;
}
$name .= "()";
return $name;
}
}
}*/
else if (($node instanceof Tolerant\Node\Expression\CallExpression &&
($access = $node->callableExpression) instanceof Tolerant\Node\Expression\MemberAccessExpression) || (
($access = $node) instanceof Tolerant\Node\Expression\MemberAccessExpression
)) {
if ($access->memberName instanceof Tolerant\Node\Expression) {
// Cannot get definition if right-hand side is expression // Cannot get definition if right-hand side is expression
return null; return null;
} }
// Get the type of the left-hand expression // Get the type of the left-hand expression
$varType = $this->resolveExpressionNodeToType($node->var); $varType = $this->resolveExpressionNodeToType($access->dereferencableExpression);
if ($varType instanceof Types\Compound) { if ($varType instanceof Types\Compound) {
// For compound types, use the first FQN we find // For compound types, use the first FQN we find
// (popular use case is ClassName|null) // (popular use case is ClassName|null)
@ -302,8 +326,8 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} else { } else {
$classFqn = substr((string)$varType->getFqsen(), 1); $classFqn = substr((string)$varType->getFqsen(), 1);
} }
$memberSuffix = '->' . (string)$node->name; $memberSuffix = '->' . (string)($access->memberName->getText() ?? $access->memberName->getText($node->getFileContents()));
if ($node instanceof Node\Expr\MethodCall) { if ($node instanceof Tolerant\Node\Expression\CallExpression) {
$memberSuffix .= '()'; $memberSuffix .= '()';
} }
// Find the right class that implements the member // Find the right class that implements the member
@ -327,74 +351,86 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
} }
return $classFqn . $memberSuffix; return $classFqn . $memberSuffix;
} else if ($parent instanceof Node\Expr\FuncCall && $node instanceof Node\Name) { }
else if ($parent->parent instanceof Tolerant\Node\Expression\CallExpression && $node instanceof Tolerant\Node\DelimitedList\QualifiedNameParts) {
if ($parent->name instanceof Node\Expr) { if ($parent->name instanceof Node\Expr) {
return null; return null;
} }
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name); $name = (string)($parent->getNamespacedName());
} else if ($parent instanceof Node\Expr\ConstFetch && $node instanceof Node\Name) { }
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name); else if ($this->isConstantFetch($node)) {
} else if ( $name = (string)($node->getNamespacedName());
$node instanceof Node\Expr\ClassConstFetch }
|| $node instanceof Node\Expr\StaticPropertyFetch else if (
|| $node instanceof Node\Expr\StaticCall ($scoped = $node) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|| ($node instanceof Tolerant\Node\Expression\CallExpression && ($scoped = $node->callableExpression) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression)
) { ) {
if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) { if ($scoped->memberName instanceof Tolerant\Node\Expression) {
// Cannot get definition of dynamic names // Cannot get definition of dynamic names
return null; return null;
} }
$className = (string)$node->class; $className = (string)$scoped->scopeResolutionQualifier->getText();
if ($className === 'self' || $className === 'static' || $className === 'parent') { if ($className === 'self' || $className === 'static' || $className === 'parent') {
// self and static are resolved to the containing class // self and static are resolved to the containing class
$classNode = getClosestNode($node, Node\Stmt\Class_::class); $classNode = $node->getFirstAncestor(Tolerant\Node\Statement\ClassDeclaration::class);
if ($classNode === null) { if ($classNode === null) {
return null; return null;
} }
if ($className === 'parent') { if ($className === 'parent') {
// parent is resolved to the parent class // parent is resolved to the parent class
if (!isset($n->extends)) { if (!isset($node->extends)) {
return null; return null;
} }
$className = (string)$classNode->extends; $className = (string)$classNode->extends;
} else { } else {
$className = (string)$classNode->namespacedName; $className = (string)$classNode->getNamespacedName();
} }
} }
if ($node instanceof Node\Expr\StaticPropertyFetch) { if ($scoped->memberName instanceof Tolerant\Node\Expression\Variable) {
$name = (string)$className . '::$' . $node->name; $name = (string)$className . '::$' . $scoped->memberName->getName();
} else { } else {
$name = (string)$className . '::' . $node->name; $name = (string)$className . '::' . $scoped->memberName->getText($node->getFileContents());
} }
} else { }
else {
return null; return null;
} }
if (!isset($name)) { if (!isset($name)) {
return null; return null;
} }
if ( if (
$node instanceof Node\Expr\MethodCall $node instanceof Tolerant\Node\Expression\CallExpression
|| $node instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\FuncCall
) { ) {
$name .= '()'; $name .= '()';
} }
return $name; return $name;
} }
private function isConstantFetch(Tolerant\Node $node) : bool {
return
($node->parent instanceof Tolerant\Node\Statement\ExpressionStatement || $node->parent instanceof Tolerant\Node\Expression) &&
!(
$node->parent instanceof Tolerant\Node\Expression\MemberAccessExpression || $node->parent instanceof Tolerant\Node\Expression\CallExpression ||
$node->parent instanceof Tolerant\Node\Expression\ObjectCreationExpression ||
$node->parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression || $node->parent instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression ||
($node->parent instanceof Tolerant\Node\Expression\BinaryExpression && $node->parent->operator->kind === Tolerant\TokenKind::InstanceOfKeyword)
);
}
/** /**
* Returns FQN of the class a node is contained in * Returns FQN of the class a node is contained in
* Returns null if the class is anonymous or the node is not contained in a class * Returns null if the class is anonymous or the node is not contained in a class
* *
* @param Node $node * @param Tolerant\Node $node
* @return string|null * @return string|null
*/ */
private static function getContainingClassFqn(Node $node) private static function getContainingClassFqn(Tolerant\Node $node)
{ {
$classNode = getClosestNode($node, Node\Stmt\Class_::class); $classNode = $node->getFirstAncestor(Tolerant\Node\Statement\ClassDeclaration::class);
if ($classNode === null || $classNode->isAnonymous()) { if ($classNode === null) {
return null; return null;
} }
return (string)$classNode->namespacedName; return (string)$classNode->getNamespacedName();
} }
/** /**
@ -403,31 +439,34 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
* @param Node\Expr\Variable|Node\Expr\ClosureUse $var The variable access * @param Node\Expr\Variable|Node\Expr\ClosureUse $var The variable access
* @return Node\Expr\Assign|Node\Expr\AssignOp|Node\Param|Node\Expr\ClosureUse|null * @return Node\Expr\Assign|Node\Expr\AssignOp|Node\Param|Node\Expr\ClosureUse|null
*/ */
public static function resolveVariableToNode(Node\Expr $var) private static function resolveVariableToNode(Tolerant\Node $var)
{ {
$n = $var; $n = $var;
// When a use is passed, start outside the closure to not return immediatly // When a use is passed, start outside the closure to not return immediately
if ($var instanceof Node\Expr\ClosureUse) { if ($var instanceof Tolerant\Node\UseVariableName) {
$n = $var->getAttribute('parentNode')->getAttribute('parentNode'); $n = $var->getFirstAncestor(Tolerant\Node\Expression\AnonymousFunctionCreationExpression::class);
$name = $var->var; $name = $var->getName();
} else if ($var instanceof Node\Expr\Variable || $var instanceof Node\Param) { } else if ($var instanceof Tolerant\Node\Expression\Variable || $var instanceof Tolerant\Node\Parameter) {
$name = $var->name; $name = $var->getName();
} else { } else {
throw new \InvalidArgumentException('$var must be Variable, Param or ClosureUse, not ' . get_class($var)); throw new \InvalidArgumentException('$var must be Variable, Param or ClosureUse, not ' . get_class($var));
} }
// Traverse the AST up // Traverse the AST up
do { do {
// If a function is met, check the parameters and use statements // If a function is met, check the parameters and use statements
if ($n instanceof Node\FunctionLike) { if (self::isFunctionLike($n)) {
foreach ($n->getParams() as $param) { if ($n->parameters !== null) {
if ($param->name === $name) {
foreach ($n->parameters->getElements() as $param) {
if ($param->getName() === $name) {
return $param; return $param;
} }
} }
}
// If it is a closure, also check use statements // If it is a closure, also check use statements
if ($n instanceof Node\Expr\Closure) { if ($n instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression) {
foreach ($n->uses as $use) { foreach ($n->anonymousFunctionUseClause->useVariableNameList->getElements() as $use) {
if ($use->var === $name) { if ($use->getName() === $name) {
return $use; return $use;
} }
} }
@ -435,19 +474,37 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
break; break;
} }
// Check each previous sibling node for a variable assignment to that variable // Check each previous sibling node for a variable assignment to that variable
while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) { while ($n->getPreviousSibling() && $n = $n->getPreviousSibling()) {
if ($n instanceof Tolerant\Node\Statement\ExpressionStatement) {
$n = $n->expression;
}
if ( if (
($n instanceof Node\Expr\Assign || $n instanceof Node\Expr\AssignOp) ($n instanceof Tolerant\Node\Expression\AssignmentExpression && $n->operator->kind === Tolerant\TokenKind::EqualsToken)
&& $n->var instanceof Node\Expr\Variable && $n->var->name === $name && $n->leftOperand instanceof Tolerant\Node\Expression\Variable && $n->leftOperand->getName() === $name
) { ) {
return $n; return $n;
} }
} }
} while (isset($n) && $n = $n->getAttribute('parentNode')); } while (isset($n) && $n = $n->getParent());
// Return null if nothing was found // Return null if nothing was found
return null; return null;
} }
function getFunctionLikeDeclarationFromParameter(Tolerant\Node $node) {
return $node->getFirstAncestor(
Tolerant\Node\Statement\FunctionDeclaration::class,
Tolerant\Node\MethodDeclaration::class,
Tolerant\Node\Expression\AnonymousFunctionCreationExpression::class
);
}
static function isFunctionLike(Tolerant\Node $node) {
return
$node instanceof Tolerant\Node\Statement\FunctionDeclaration ||
$node instanceof Tolerant\Node\MethodDeclaration ||
$node instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression;
}
/** /**
* 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.
@ -457,51 +514,86 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
*/ */
public function resolveExpressionNodeToType($expr): Type public function resolveExpressionNodeToType($expr): Type
{ {
if ($expr instanceof Node\Expr\Variable || $expr instanceof Node\Expr\ClosureUse) { if ($expr instanceof Tolerant\Node\Expression\Variable || $expr instanceof Tolerant\Node\UseVariableName) {
if ($expr instanceof Node\Expr\Variable && $expr->name === 'this') { if ($expr instanceof Tolerant\Node\Expression\Variable && $expr->getName() === 'this') {
return new Types\This; return new Types\This;
} }
// Find variable definition // Find variable definition
$defNode = $this->resolveVariableToNode($expr); $defNode = $this->resolveVariableToNode($expr);
if ($defNode instanceof Node\Expr) { if ($defNode instanceof Tolerant\Node\Expression) {
return $this->resolveExpressionNodeToType($defNode); return $this->resolveExpressionNodeToType($defNode);
} }
if ($defNode instanceof Node\Param) { if ($defNode instanceof Tolerant\Node\Parameter) {
return $this->getTypeFromNode($defNode); return $this->getTypeFromNode($defNode);
} }
} }
if ($expr instanceof Node\Expr\FuncCall) { if ($expr instanceof Tolerant\Node\Expression\CallExpression &&
!($expr->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
$expr->callableExpression instanceof Tolerant\Node\Expression\MemberAccessExpression)) {
// Find the function definition // Find the function definition
if ($expr->name instanceof Node\Expr) { if ($expr->callableExpression instanceof Tolerant\Node\Expression) {
// Cannot get type for dynamic function call // Cannot get type for dynamic function call
return new Types\Mixed; return new Types\Mixed;
} }
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
if ($expr->callableExpression instanceof Tolerant\Node\QualifiedName) {
$fqn = $expr->callableExpression->getResolvedName() ?? $expr->callableExpression->getNamespacedName();
$fqn .= "()";
$def = $this->index->getDefinition($fqn, true); $def = $this->index->getDefinition($fqn, true);
if ($def !== null) { if ($def !== null) {
return $def->type; return $def->type;
} }
} }
if ($expr instanceof Node\Expr\ConstFetch) {
if (strtolower((string)$expr->name) === 'true' || strtolower((string)$expr->name) === 'false') { /*
$isScopedPropertyAccess = $expr->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression;
$prefix = $isScopedPropertyAccess ?
$expr->callableExpression->scopeResolutionQualifier : $expr->callableExpression->dereferencableExpression;
if ($prefix instanceof Tolerant\Node\QualifiedName) {
$name = $prefix->getNamespacedName() ?? $prefix->getText();
} elseif ($prefix instanceof Tolerant\Token) {
// TODO DOES THIS EVER HAPPEN?
$name = $prefix->getText($expr->getText());
}
if (isset($name)) {
$memberNameText = $expr->callableExpression->memberName instanceof Node
? $expr->callableExpression->memberName->getText() : $expr->callableExpression->memberName->getText($expr->getFileContents());
$fqn = $name . ($isScopedPropertyAccess ? "::" : "->") . $memberNameText . "()";
$def = $this->index->getDefinition($fqn, true);
if ($def !== null) {
return $def->type;
}
}*/
}
if (strtolower((string)$expr->getText()) === 'true' || strtolower((string)$expr->getText()) === 'false') {
return new Types\Boolean; return new Types\Boolean;
} }
if (strtolower((string)$expr->name) === 'null') {
return new Types\Null_; if ($this->isConstantFetch($expr)) {
}
// Resolve constant // Resolve constant
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name); if ($expr instanceof Tolerant\Node\QualifiedName) {
$fqn = (string)$expr->getNamespacedName();
$def = $this->index->getDefinition($fqn, true); $def = $this->index->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) { if (($expr instanceof Tolerant\Node\Expression\CallExpression &&
($access = $expr->callableExpression) instanceof Tolerant\Node\Expression\MemberAccessExpression)
|| ($access = $expr) instanceof Tolerant\Node\Expression\MemberAccessExpression) {
if ($access->memberName instanceof Tolerant\Node\Expression) {
return new Types\Mixed; return new Types\Mixed;
} }
$var = $access->dereferencableExpression;
// Resolve object // Resolve object
$objType = $this->resolveExpressionNodeToType($expr->var); $objType = $this->resolveExpressionNodeToType($var);
if (!($objType instanceof Types\Compound)) { if (!($objType instanceof Types\Compound)) {
$objType = new Types\Compound([$objType]); $objType = new Types\Compound([$objType]);
} }
@ -516,8 +608,8 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} else { } else {
$classFqn = substr((string)$t->getFqsen(), 1); $classFqn = substr((string)$t->getFqsen(), 1);
} }
$fqn = $classFqn . '->' . $expr->name; $fqn = $classFqn . '->' . $access->memberName->getText($expr->getFileContents());
if ($expr instanceof Node\Expr\MethodCall) { if ($expr instanceof Tolerant\Node\Expression\CallExpression) {
$fqn .= '()'; $fqn .= '()';
} }
$def = $this->index->getDefinition($fqn); $def = $this->index->getDefinition($fqn);
@ -527,20 +619,19 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
} }
if ( if (
$expr instanceof Node\Expr\StaticCall $expr instanceof Tolerant\Node\Expression\CallExpression && ($scopedAccess = $expr->callableExpression) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|| $expr instanceof Node\Expr\StaticPropertyFetch || ($scopedAccess = $expr) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|| $expr instanceof Node\Expr\ClassConstFetch
) { ) {
$classType = self::resolveClassNameToType($expr->class); $classType = self::resolveClassNameToType($scopedAccess->scopeResolutionQualifier);
if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null || $expr->name instanceof Node\Expr) { if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null /*|| $expr->name instanceof Tolerant\Node\Expression*/) {
return new Types\Mixed; return new Types\Mixed;
} }
$fqn = substr((string)$classType->getFqsen(), 1) . '::'; $fqn = substr((string)$classType->getFqsen(), 1) . '::';
if ($expr instanceof Node\Expr\StaticPropertyFetch) { if ($expr instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression && $expr->memberName instanceof Tolerant\Node\Expression\Variable) {
$fqn .= '$'; $fqn .= '$';
} }
$fqn .= $expr->name; $fqn .= $scopedAccess->memberName->getText() ?? $scopedAccess->memberName->getText($expr->getFileContents()); // TODO is there a cleaner way to do this?
if ($expr instanceof Node\Expr\StaticCall) { if ($expr instanceof Tolerant\Node\Expression\CallExpression) {
$fqn .= '()'; $fqn .= '()';
} }
$def = $this->index->getDefinition($fqn); $def = $this->index->getDefinition($fqn);
@ -549,121 +640,110 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
return $def->type; return $def->type;
} }
if ($expr instanceof Node\Expr\New_) { if ($expr instanceof Tolerant\Node\Expression\ObjectCreationExpression) {
return self::resolveClassNameToType($expr->class); return self::resolveClassNameToType($expr->classTypeDesignator);
} }
if ($expr instanceof Node\Expr\Clone_ || $expr instanceof Node\Expr\Assign) { if ($expr instanceof Tolerant\Node\Expression\CloneExpression) {
return $this->resolveExpressionNodeToType($expr->expr); return $this->resolveExpressionNodeToType($expr->expression);
} }
if ($expr instanceof Node\Expr\Ternary) { if ($expr instanceof Tolerant\Node\Expression\AssignmentExpression) {
return $this->resolveExpressionNodeToType($expr->rightOperand);
}
if ($expr instanceof Tolerant\Node\Expression\TernaryExpression) {
// ?: // ?:
if ($expr->if === null) { if ($expr->ifExpression === null) {
return new Types\Compound([ return new Types\Compound([
$this->resolveExpressionNodeToType($expr->cond), $this->resolveExpressionNodeToType($expr->condition), // why?
$this->resolveExpressionNodeToType($expr->else) $this->resolveExpressionNodeToType($expr->elseExpression)
]); ]);
} }
// Ternary is a compound of the two possible values // Ternary is a compound of the two possible values
return new Types\Compound([ return new Types\Compound([
$this->resolveExpressionNodeToType($expr->if), $this->resolveExpressionNodeToType($expr->ifExpression),
$this->resolveExpressionNodeToType($expr->else) $this->resolveExpressionNodeToType($expr->elseExpression)
]); ]);
} }
if ($expr instanceof Node\Expr\BinaryOp\Coalesce) { if ($expr instanceof Tolerant\Node\Expression\BinaryExpression && $expr->operator->kind === Tolerant\TokenKind::QuestionQuestionToken) {
// ?? operator // ?? operator
return new Types\Compound([ return new Types\Compound([
$this->resolveExpressionNodeToType($expr->left), $this->resolveExpressionNodeToType($expr->leftOperand),
$this->resolveExpressionNodeToType($expr->right) $this->resolveExpressionNodeToType($expr->rightOperand)
]); ]);
} }
if ( if (
$expr instanceof Node\Expr\Instanceof_ $this->isBooleanExpression($expr)
|| $expr instanceof Node\Expr\Cast\Bool_
|| $expr instanceof Node\Expr\BooleanNot || ($expr instanceof Tolerant\Node\Expression\CastExpression && $expr->castType->kind === Tolerant\TokenKind::BoolCastToken)
|| $expr instanceof Node\Expr\Empty_ || ($expr instanceof Tolerant\Node\Expression\UnaryOpExpression && $expr->operator->kind === Tolerant\TokenKind::ExclamationToken)
|| $expr instanceof Node\Expr\Isset_ || $expr instanceof Tolerant\Node\Expression\EmptyIntrinsicExpression
|| $expr instanceof Node\Expr\BinaryOp\Greater || $expr instanceof Tolerant\Node\Expression\IssetIntrinsicExpression
|| $expr instanceof Node\Expr\BinaryOp\GreaterOrEqual
|| $expr instanceof Node\Expr\BinaryOp\Smaller
|| $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual
|| $expr instanceof Node\Expr\BinaryOp\BooleanAnd
|| $expr instanceof Node\Expr\BinaryOp\BooleanOr
|| $expr instanceof Node\Expr\BinaryOp\LogicalAnd
|| $expr instanceof Node\Expr\BinaryOp\LogicalOr
|| $expr instanceof Node\Expr\BinaryOp\LogicalXor
|| $expr instanceof Node\Expr\BinaryOp\NotEqual
|| $expr instanceof Node\Expr\BinaryOp\NotIdentical
) { ) {
return new Types\Boolean; return new Types\Boolean;
} }
if ( if (
$expr instanceof Node\Expr\Cast\String_ ($expr instanceof Tolerant\Node\Expression\BinaryExpression &&
|| $expr instanceof Node\Expr\BinaryOp\Concat ($expr->operator->kind === Tolerant\TokenKind::DotToken || $expr->operator->kind === Tolerant\TokenKind::DotEqualsToken)) ||
|| $expr instanceof Node\Expr\AssignOp\Concat $expr instanceof Tolerant\Node\StringLiteral ||
|| $expr instanceof Node\Scalar\String_ ($expr instanceof Tolerant\Node\Expression\CastExpression && $expr->castType->kind === Tolerant\TokenKind::StringCastToken)
|| $expr instanceof Node\Scalar\Encapsed
|| $expr instanceof Node\Scalar\EncapsedStringPart // TODO
|| $expr instanceof Node\Scalar\MagicConst\Class_ // || $expr instanceof Node\Expr\Scalar\String_
|| $expr instanceof Node\Scalar\MagicConst\Dir // || $expr instanceof Node\Expr\Scalar\Encapsed
|| $expr instanceof Node\Scalar\MagicConst\Function_ // || $expr instanceof Node\Expr\Scalar\EncapsedStringPart
|| $expr instanceof Node\Scalar\MagicConst\Method // || $expr instanceof Node\Expr\Scalar\MagicConst\Class_
|| $expr instanceof Node\Scalar\MagicConst\Namespace_ // || $expr instanceof Node\Expr\Scalar\MagicConst\Dir
|| $expr instanceof Node\Scalar\MagicConst\Trait_ // || $expr instanceof Node\Expr\Scalar\MagicConst\Function_
// || $expr instanceof Node\Expr\Scalar\MagicConst\Method
// || $expr instanceof Node\Expr\Scalar\MagicConst\Namespace_
// || $expr instanceof Node\Expr\Scalar\MagicConst\Trait_
) { ) {
return new Types\String_; return new Types\String_;
} }
if ( if (
$expr instanceof Node\Expr\BinaryOp\Minus $expr instanceof Tolerant\Node\Expression\BinaryExpression &&
|| $expr instanceof Node\Expr\BinaryOp\Plus ($operator = $expr->operator->kind)
|| $expr instanceof Node\Expr\BinaryOp\Pow && ($operator === Tolerant\TokenKind::PlusToken ||
|| $expr instanceof Node\Expr\BinaryOp\Mul $operator === Tolerant\TokenKind::AsteriskAsteriskToken ||
$operator === Tolerant\TokenKind::AsteriskToken ||
$operator === Tolerant\TokenKind::MinusToken ||
$operator === Tolerant\TokenKind::AsteriskEqualsToken||
$operator === Tolerant\TokenKind::AsteriskAsteriskEqualsToken ||
$operator === Tolerant\TokenKind::MinusEqualsToken ||
$operator === Tolerant\TokenKind::PlusEqualsToken // TODO - this should be a type of assigment expression
)
) { ) {
if ( if (
$this->resolveExpressionNodeToType($expr->left) instanceof Types\Integer $this->resolveExpressionNodeToType($expr->leftOperand) instanceof Types\Integer_
&& $this->resolveExpressionNodeToType($expr->right) instanceof Types\Integer && $this->resolveExpressionNodeToType($expr->rightOperand) instanceof Types\Integer_
) { ) {
return new Types\Integer; return new Types\Integer;
} }
return new Types\Float_; return new Types\Float_;
} }
if ( if (
$expr instanceof Node\Expr\AssignOp\Minus // TODO better naming
|| $expr instanceof Node\Expr\AssignOp\Plus ($expr instanceof Tolerant\Node\NumericLiteral && $expr->children->kind === Tolerant\TokenKind::IntegerLiteralToken) ||
|| $expr instanceof Node\Expr\AssignOp\Pow $expr instanceof Tolerant\Node\Expression\BinaryExpression && (
|| $expr instanceof Node\Expr\AssignOp\Mul ($operator = $expr->operator->kind)
) { && ($operator === Tolerant\TokenKind::LessThanEqualsGreaterThanToken ||
if ( $operator === Tolerant\TokenKind::AmpersandToken ||
$this->resolveExpressionNodeToType($expr->var) instanceof Types\Integer $operator === Tolerant\TokenKind::CaretToken ||
&& $this->resolveExpressionNodeToType($expr->expr) instanceof Types\Integer $operator === Tolerant\TokenKind::BarToken)
) { )
return new Types\Integer;
}
return new Types\Float_;
}
if (
$expr instanceof Node\Scalar\LNumber
|| $expr instanceof Node\Expr\Cast\Int_
|| $expr instanceof Node\Scalar\MagicConst\Line
|| $expr instanceof Node\Expr\BinaryOp\Spaceship
|| $expr instanceof Node\Expr\BinaryOp\BitwiseAnd
|| $expr instanceof Node\Expr\BinaryOp\BitwiseOr
|| $expr instanceof Node\Expr\BinaryOp\BitwiseXor
) { ) {
return new Types\Integer; return new Types\Integer;
} }
if ( if (
$expr instanceof Node\Expr\BinaryOp\Div $expr instanceof Tolerant\Node\NumericLiteral && $expr->children->kind === Tolerant\TokenKind::FloatingLiteralToken
|| $expr instanceof Node\Scalar\DNumber ||
|| $expr instanceof Node\Expr\Cast\Double ($expr instanceof Tolerant\Node\Expression\CastExpression && $expr->castType->kind === Tolerant\TokenKind::DoubleCastToken)
) { ) {
return new Types\Float_; return new Types\Float_;
} }
if ($expr instanceof Node\Expr\Array_) { if ($expr instanceof Tolerant\Node\Expression\ArrayCreationExpression) {
$valueTypes = []; $valueTypes = [];
$keyTypes = []; $keyTypes = [];
foreach ($expr->items as $item) { foreach ($expr->arrayElements->getElements() as $item) {
$valueTypes[] = $this->resolveExpressionNodeToType($item->value); $valueTypes[] = $this->resolveExpressionNodeToType($item->value);
$keyTypes[] = $item->key ? $this->resolveExpressionNodeToType($item->key) : new Types\Integer; $keyTypes[] = $item->key ? $this->resolveExpressionNodeToType($item->key) : new Types\Integer;
} }
@ -685,48 +765,74 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
return new Types\Array_($valueType, $keyType); return new Types\Array_($valueType, $keyType);
} }
if ($expr instanceof Node\Expr\ArrayDimFetch) { if ($expr instanceof Tolerant\Node\Expression\SubscriptExpression) {
$varType = $this->resolveExpressionNodeToType($expr->var); $varType = $this->resolveExpressionNodeToType($expr->postfixExpression);
if (!($varType instanceof Types\Array_)) { if (!($varType instanceof Types\Array_)) {
return new Types\Mixed; return new Types\Mixed;
} }
return $varType->getValueType(); return $varType->getValueType();
} }
if ($expr instanceof Node\Expr\Include_) { if ($expr instanceof Tolerant\Node\Expression\ScriptInclusionExpression) {
// TODO: resolve path to PhpDocument and find return statement // TODO: resolve path to PhpDocument and find return statement
return new Types\Mixed; return new Types\Mixed;
} }
return new Types\Mixed; return new Types\Mixed;
} }
private function isBooleanExpression($expression) : bool {
if (!($expression instanceof Tolerant\Node\Expression\BinaryExpression)) {
return false;
}
switch ($expression->operator->kind) {
case Tolerant\TokenKind::InstanceOfKeyword:
case Tolerant\TokenKind::GreaterThanToken:
case Tolerant\TokenKind::GreaterThanEqualsToken:
case Tolerant\TokenKind::LessThanToken:
case Tolerant\TokenKind::LessThanEqualsToken:
case Tolerant\TokenKind::AndKeyword:
case Tolerant\TokenKind::AmpersandAmpersandToken:
case Tolerant\TokenKind::LessThanEqualsGreaterThanToken:
case Tolerant\TokenKind::OrKeyword:
case Tolerant\TokenKind::BarBarToken:
case Tolerant\TokenKind::XorKeyword:
case Tolerant\TokenKind::ExclamationEqualsEqualsToken:
case Tolerant\TokenKind::ExclamationEqualsToken:
case Tolerant\TokenKind::CaretToken:
case Tolerant\TokenKind::EqualsEqualsEqualsToken:
case Tolerant\TokenKind::EqualsToken:
return true;
}
return false;
}
/** /**
* Takes any class name node (from a static method call, or new node) and returns a Type object * 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 * Resolves keywords like self, static and parent
* *
* @param Node $class * @param Tolerant\Node || Tolerant\Token $class
* @return Type * @return Type
*/ */
private static function resolveClassNameToType(Node $class): Type private static function resolveClassNameToType($class): Type
{ {
if ($class instanceof Node\Expr) { if ($class instanceof Tolerant\Node\Expression) {
return new Types\Mixed; return new Types\Mixed;
} }
if ($class instanceof Node\Stmt\Class_) { if ($class instanceof Tolerant\Token && $class->kind === Tolerant\TokenKind::ClassKeyword) {
// Anonymous class // Anonymous class
return new Types\Object_; return new Types\Object_;
} }
$className = (string)$class; $className = (string)$class->getResolvedName();
if ($className === 'static') { if ($className === 'static') {
return new Types\Static_; return new Types\Static_;
} }
if ($className === 'self' || $className === 'parent') { if ($className === 'self' || $className === 'parent') {
$classNode = getClosestNode($class, Node\Stmt\Class_::class); $classNode = $class->getFirstAncestor(Tolerant\Node\Statement\ClassDeclaration::class);
if ($className === 'parent') { if ($className === 'parent') {
if ($classNode === null || $classNode->extends === null) { if ($classNode === null || $classNode->classBaseClause === null || $classNode->classBaseClause->baseClass === null) {
return new Types\Object_; return new Types\Object_;
} }
// parent is resolved to the parent class // parent is resolved to the parent class
$classFqn = (string)$classNode->extends; $classFqn = (string)$classNode->classBaseClause->baseClass->getResolvedName();
} else { } else {
if ($classNode === null) { if ($classNode === null) {
return new Types\Self_; return new Types\Self_;
@ -750,33 +856,33 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
* If it is unknown, will be Types\Mixed. * If it is unknown, will be Types\Mixed.
* Returns null if the node does not have a type. * Returns null if the node does not have a type.
* *
* @param Node $node * @param Tolerant\Node $node
* @return \phpDocumentor\Reflection\Type|null * @return \phpDocumentor\Reflection\Type|null
*/ */
public function getTypeFromNode($node) public function getTypeFromNode($node)
{ {
if ($node instanceof Node\Param) { // For parameters, get the type of the parameter [first from doc block, then from param type]
if ($node instanceof Tolerant\Node\Parameter) {
// Parameters // Parameters
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock'); // Get the doc block for the the function call
$functionLikeDeclaration = $this->getFunctionLikeDeclarationFromParameter($node);
$variableName = $node->variableName->getText($node->getFileContents());
$docBlock = $this->getDocBlock($functionLikeDeclaration);
if ($docBlock !== null) { if ($docBlock !== null) {
// Use @param tag $parameterDocBlockTag = $this->getDocBlockTagForParameter($docBlock, $variableName);
foreach ($docBlock->getTagsByName('param') as $paramTag) { if ($parameterDocBlockTag !== null && $parameterDocBlockTag->getType() !== null) {
if ($paramTag->getVariableName() === $node->name) { return $parameterDocBlockTag->getType();
if ($paramTag->getType() === null) {
break;
}
return $paramTag->getType();
} }
} }
}
$type = null; if ($node->typeDeclaration !== null) {
if ($node->type !== null) {
// Use PHP7 return type hint // Use PHP7 return type hint
if (is_string($node->type)) { if ($node->typeDeclaration instanceof Tolerant\Token) {
// Resolve a string like "bool" to a type object // Resolve a string like "bool" to a type object
$type = $this->typeResolver->resolve($node->type); $type = $this->typeResolver->resolve($node->typeDeclaration->getText($node->getFileContents()));
} else { } else {
$type = new Types\Object_(new Fqsen('\\' . (string)$node->type)); $type = new Types\Object_(new Fqsen('\\' . (string)$node->typeDeclaration->getResolvedName()));
} }
} }
if ($node->default !== null) { if ($node->default !== null) {
@ -789,9 +895,10 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
return $type ?? new Types\Mixed; return $type ?? new Types\Mixed;
} }
if ($node instanceof Node\FunctionLike) { // for functions and methods, get the return type [first from doc block, then from return type]
if ($this->isFunctionLike($node)) {
// Functions/methods // Functions/methods
$docBlock = $node->getAttribute('docBlock'); $docBlock = $this->getDocBlock($node);
if ( if (
$docBlock !== null $docBlock !== null
&& !empty($returnTags = $docBlock->getTagsByName('return')) && !empty($returnTags = $docBlock->getTagsByName('return'))
@ -802,47 +909,47 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
if ($node->returnType !== null) { if ($node->returnType !== null) {
// Use PHP7 return type hint // Use PHP7 return type hint
if (is_string($node->returnType)) { if ($node->returnType instanceof Tolerant\Token) {
// Resolve a string like "bool" to a type object // Resolve a string like "bool" to a type object
return $this->typeResolver->resolve($node->returnType); return $this->typeResolver->resolve($node->returnType->getText($node->getFileContents()));
} }
return new Types\Object_(new Fqsen('\\' . (string)$node->returnType)); return new Types\Object_(new Fqsen('\\' . (string)$node->returnType->getResolvedName()));
} }
// Unknown return type // Unknown return type
return new Types\Mixed; return new Types\Mixed;
} }
if ($node instanceof Node\Expr\Variable) {
$node = $node->getAttribute('parentNode'); // for variables / assignments, get the documented type the assignment resolves to.
if ($node instanceof Tolerant\Node\Expression\Variable) {
$node = $node->getFirstAncestor(Tolerant\Node\Expression\AssignmentExpression::class) ?? $node;
} }
if ( if (
$node instanceof Node\Stmt\PropertyProperty ($declarationNode = $node->getFirstAncestor(
|| $node instanceof Node\Const_ Tolerant\Node\PropertyDeclaration::class,
|| $node instanceof Node\Expr\Assign Tolerant\Node\Statement\ConstDeclaration::class,
|| $node instanceof Node\Expr\AssignOp Tolerant\Node\ClassConstDeclaration::class)) !== null ||
) { $node instanceof Tolerant\Node\Expression\AssignmentExpression)
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) { {
$docBlockHolder = $node->getAttribute('parentNode'); $declarationNode = $declarationNode ?? $node;
} else {
$docBlockHolder = $node;
}
// Property, constant or variable // Property, constant or variable
// Use @var tag // Use @var tag
if ( if (
isset($docBlockHolder) ($docBlock = $this->getDocBlock($declarationNode))
&& ($docBlock = $docBlockHolder->getAttribute('docBlock'))
&& !empty($varTags = $docBlock->getTagsByName('var')) && !empty($varTags = $docBlock->getTagsByName('var'))
&& ($type = $varTags[0]->getType()) && ($type = $varTags[0]->getType())
) { ) {
return $type; return $type;
} }
// Resolve the expression // Resolve the expression
if ($node instanceof Node\Stmt\PropertyProperty) { if ($declarationNode instanceof Tolerant\Node\PropertyDeclaration) {
if ($node->default) { // TODO should have default
return $this->resolveExpressionNodeToType($node->default); if (isset($node->rightOperand)) {
return $this->resolveExpressionNodeToType($node->rightOperand);
} }
} else if ($node instanceof Node\Const_) { } else if ($node instanceof Tolerant\Node\ConstElement) {
return $this->resolveExpressionNodeToType($node->value); return $this->resolveExpressionNodeToType($node->assignment);
} else { } else if ($node instanceof Tolerant\Node\Expression\AssignmentExpression) {
return $this->resolveExpressionNodeToType($node); return $this->resolveExpressionNodeToType($node);
} }
// TODO: read @property tags of class // TODO: read @property tags of class
@ -853,6 +960,20 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return null; return null;
} }
/**
* @param DocBlock $docBlock
* @param $variableName
* @return DocBlock\Tags\Param | null
*/
private function getDocBlockTagForParameter($docBlock, $variableName) {
$tags = $docBlock->getTagsByName('param');
foreach ($tags as $tag) {
if ($tag->getVariableName() === $variableName) {
return $tag;
}
}
}
/** /**
* Returns the fully qualified name (FQN) that is defined by a node * 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 * Returns null if the node does not declare any symbol that can be referenced by an FQN

View File

@ -24,8 +24,9 @@ class GlobalTest extends ServerTestCase
// namespace keyword // namespace keyword
$result = $this->textDocument->definition( $result = $this->textDocument->definition(
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))), new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
new Position(2, 4) new Position(1, 0)
)->wait(); )->wait();
var_dump($result);
$this->assertEquals([], $result); $this->assertEquals([], $result);
} }

View File

@ -21,7 +21,7 @@ class HoverTest extends ServerTestCase
$reference->range->start $reference->range->start
)->wait(); )->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"), new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' 'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
], $reference->range), $result); ], $reference->range), $result);
} }
@ -36,7 +36,7 @@ class HoverTest extends ServerTestCase
$definition->range->start $definition->range->start
)->wait(); )->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"), new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' 'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
], $definition->range), $result); ], $definition->range), $result);
} }
@ -51,7 +51,7 @@ class HoverTest extends ServerTestCase
$reference->range->end $reference->range->end
)->wait(); )->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter) : \TestInterface"), new MarkedString('php', "<?php\npublic function testMethod(\$testParameter) : TestInterface"),
'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.' 'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.'
], $reference->range), $result); ], $reference->range), $result);
} }
@ -153,7 +153,7 @@ class HoverTest extends ServerTestCase
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait(); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait();
$this->assertEquals(new Hover( $this->assertEquals(new Hover(
[new MarkedString('php', "<?php\n\$var = 123;")], [new MarkedString('php', "<?php\n\$var = 123")],
new Range(new Position(13, 5), new Position(13, 9)) new Range(new Position(13, 5), new Position(13, 9))
), $result); ), $result);
} }
@ -166,7 +166,7 @@ class HoverTest extends ServerTestCase
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait(); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait();
$this->assertEquals(new Hover( $this->assertEquals(new Hover(
[ [
new MarkedString('php', "<?php\n\TestNamespace\TestClass \$param"), new MarkedString('php', "<?php\nTestClass \$param"),
'Adipisicing non non cillum sint incididunt cillum enim mollit.' 'Adipisicing non non cillum sint incididunt cillum enim mollit.'
], ],
new Range(new Position(22, 9), new Position(22, 15)) new Range(new Position(22, 9), new Position(22, 15))
@ -180,7 +180,7 @@ class HoverTest extends ServerTestCase
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/global_symbols.php')); $uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/global_symbols.php'));
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(59, 11))->wait(); $result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(59, 11))->wait();
$this->assertEquals(new Hover([ $this->assertEquals(new Hover([
new MarkedString('php', "<?php\nclass TestClass implements \\TestInterface"), new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' 'Pariatur ut laborum tempor voluptate consequat ea deserunt.'
], new Range(new Position(59, 8), new Position(59, 13))), $result); ], new Range(new Position(59, 8), new Position(59, 13))), $result);
} }