1
0
Fork 0

refactor definition resolver, fix misc. issues

pull/357/head
Sara Itani 2017-04-18 22:48:26 -07:00
parent 65686c6d4c
commit f5a93a2e09
15 changed files with 524 additions and 399 deletions

View File

@ -12,6 +12,7 @@ use phpDocumentor\Reflection\{
use LanguageServer\Protocol\SymbolInformation; use LanguageServer\Protocol\SymbolInformation;
use LanguageServer\Index\ReadableIndex; use LanguageServer\Index\ReadableIndex;
use Microsoft\PhpParser as Tolerant; use Microsoft\PhpParser as Tolerant;
use PhpParser\PrettyPrinterAbstract;
trait LoggedDefinitionResolverTrait trait LoggedDefinitionResolverTrait
{ {
@ -79,9 +80,14 @@ trait LoggedDefinitionResolverTrait
$resultText = $result->fqn; $resultText = $result->fqn;
} elseif ($result instanceof DocBlock) { } elseif ($result instanceof DocBlock) {
$resultText = $result->getDescription(); $resultText = $result->getDescription();
} else { }
else {
try { try {
if ($result instanceof Node) {
$resultText = (new PrettyPrinter())->prettyPrint([$result]);
} else {
$resultText = (string) $result; $resultText = (string) $result;
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
$resultText = "UNKNOWN"; $resultText = "UNKNOWN";
} }

View File

@ -53,9 +53,7 @@ class TolerantSymbolInformation
return null; return null;
} }
if ($node instanceof Node\Name) { if ($node instanceof Tolerant\Node\Expression\AssignmentExpression) {
$symbol->name = (string)$node;
} else if ($node instanceof Tolerant\Node\Expression\AssignmentExpression) {
if ($node->leftOperand instanceof Tolerant\Node\Expression\Variable) { if ($node->leftOperand instanceof Tolerant\Node\Expression\Variable) {
$symbol->name = $node->leftOperand->getName(); $symbol->name = $node->leftOperand->getName();
} elseif ($node->leftOperand instanceof Tolerant\Token) { } elseif ($node->leftOperand instanceof Tolerant\Token) {
@ -65,7 +63,11 @@ class TolerantSymbolInformation
} else if ($node instanceof Tolerant\Node\UseVariableName) { } else if ($node instanceof Tolerant\Node\UseVariableName) {
$symbol->name = $node->getName(); $symbol->name = $node->getName();
} else if (isset($node->name)) { } else if (isset($node->name)) {
$symbol->name = trim((string)$node->name->getText($node->getFileContents()), "$"); if ($node->name instanceof Tolerant\Node\QualifiedName) {
$symbol->name = (string)Tolerant\ResolvedName::buildName($node->name->nameParts, $node->getFileContents());
} else {
$symbol->name = ltrim((string)$node->name->getText($node->getFileContents()), "$");
}
} else if (isset($node->variableName)) { } else if (isset($node->variableName)) {
$symbol->name = $node->variableName->getText($node); $symbol->name = $node->variableName->getText($node);
} else { } else {

View File

@ -5,7 +5,6 @@ namespace LanguageServer;
use LanguageServer\Protocol\TolerantSymbolInformation; use LanguageServer\Protocol\TolerantSymbolInformation;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use phpDocumentor\Reflection\{ use phpDocumentor\Reflection\{
DocBlock, DocBlockFactory, Types, Type, Fqsen, TypeResolver DocBlock, DocBlockFactory, Types, Type, Fqsen, TypeResolver
}; };
@ -16,21 +15,22 @@ use Microsoft\PhpParser as Tolerant;
class TolerantDefinitionResolver implements DefinitionResolverInterface class TolerantDefinitionResolver implements DefinitionResolverInterface
{ {
/** /**
* The current project index (for retrieving existing definitions)
*
* @var \LanguageServer\Index\ReadableIndex * @var \LanguageServer\Index\ReadableIndex
*/ */
protected $index; protected $index;
/** /**
* Resolves strings to a type object.
*
* @var \phpDocumentor\Reflection\TypeResolver * @var \phpDocumentor\Reflection\TypeResolver
*/ */
private $typeResolver; private $typeResolver;
/** /**
* @var \PhpParser\PrettyPrinterAbstract * Parses Doc Block comments given the DocBlock text and import tables at a position.
*/ *
private $prettyPrinter;
/**
* @var DocBlockFactory * @var DocBlockFactory
*/ */
private $docBlockFactory; private $docBlockFactory;
@ -42,60 +42,41 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
{ {
$this->index = $index; $this->index = $index;
$this->typeResolver = new TypeResolver; $this->typeResolver = new TypeResolver;
$this->prettyPrinter = new PrettyPrinter;
$this->docBlockFactory = DocBlockFactory::createInstance(); $this->docBlockFactory = DocBlockFactory::createInstance();
} }
/** /**
* Builds the declaration line for a given node. * Builds the declaration line for a given node. Declarations with multiple lines are trimmed.
*
* *
* @param Tolerant\Node $node * @param Tolerant\Node $node
* @return string * @return string
*/ */
public function getDeclarationLineFromNode($node): string public function getDeclarationLineFromNode($node): string
{ {
// TODO Tolerant\Node\Statement\FunctionStaticDeclaration::class // If node is part of a declaration list, build a declaration line that discludes other elements in the list
// - [PropertyDeclaration] // public $a, [$b = 3], $c; => public $b = 3;
// we should have a better way of determining whether something is a property or constant // - [ConstDeclaration | ClassConstDeclaration] // "const A = 3, [B = 4];" => "const B = 4;"
// If part of a declaration list -> get the parent declaration
if ( if (
// PropertyDeclaration // public $a, $b, $c; ($declaration = TolerantParserHelpers::tryGetPropertyDeclaration($node)) && ($elements = $declaration->propertyElements) ||
$node instanceof Tolerant\Node\Expression\Variable && ($declaration = TolerantParserHelpers::tryGetConstOrClassConstDeclaration($node)) && ($elements = $declaration->constElements)
($propertyDeclaration = $node->getFirstAncestor(Tolerant\Node\PropertyDeclaration::class)) !== null
) { ) {
$defLine = $propertyDeclaration->getText(); $defLine = $declaration->getText();
$defLineStart = $propertyDeclaration->getStart(); $defLineStart = $declaration->getStart();
$defLine = \substr_replace( $defLine = \substr_replace(
$defLine, $defLine,
$node->getFullText(), $node->getFullText(),
$propertyDeclaration->propertyElements->getFullStart() - $defLineStart, $elements->getFullStart() - $defLineStart,
$propertyDeclaration->propertyElements->getFullWidth() $elements->getFullWidth()
); );
} elseif ( } else {
// ClassConstDeclaration or ConstDeclaration // const A = 1, B = 2;
$node instanceof Tolerant\Node\ConstElement &&
($constDeclaration = $node->getFirstAncestor(Tolerant\Node\Statement\ConstDeclaration::class, Tolerant\Node\ClassConstDeclaration::class))
) {
$defLine = $constDeclaration->getText();
$defLineStart = $constDeclaration->getStart();
$defLine = \substr_replace(
$defLine,
$node->getFullText(),
$constDeclaration->constElements->getFullStart() - $defLineStart,
$constDeclaration->constElements->getFullWidth()
);
}
// Get the current node
else {
$defLine = $node->getText(); $defLine = $node->getText();
} }
$defLine = \strtok($defLine, "\n"); // Trim string to only include first line
$defLine = \strtok($defLine, "\r"); $defLine = \rtrim(\strtok($defLine, "\n"), "\r");
// TODO - pretty print rather than getting text
return $defLine; return $defLine;
} }
@ -108,54 +89,60 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
*/ */
public function getDocumentationFromNode($node) public function getDocumentationFromNode($node)
{ {
// Any NamespaceDefinition comments likely apply to the file, not the declaration itself.
if ($node instanceof Tolerant\Node\Statement\NamespaceDefinition) {
return null;
}
// For properties and constants, set the node to the declaration node, rather than the individual property. // For properties and constants, set the node to the declaration node, rather than the individual property.
// This is because they get defined as part of a list. // This is because they get defined as part of a list.
$constOrPropertyDeclaration = $node->getFirstAncestor( $constOrPropertyDeclaration = TolerantParserHelpers::tryGetPropertyDeclaration($node) ?? TolerantParserHelpers::tryGetConstOrClassConstDeclaration($node);
Tolerant\Node\PropertyDeclaration::class,
Tolerant\Node\Statement\ConstDeclaration::class,
Tolerant\Node\ClassConstDeclaration::class
);
if ($constOrPropertyDeclaration !== null) { if ($constOrPropertyDeclaration !== null) {
$node = $constOrPropertyDeclaration; $node = $constOrPropertyDeclaration;
} }
// For parameters, parse the documentation to get the parameter tag. // For parameters, parse the function-like declaration to get documentation for a parameter
if ($node instanceof Tolerant\Node\Parameter) { if ($node instanceof Tolerant\Node\Parameter) {
$functionLikeDeclaration = $this->getFunctionLikeDeclarationFromParameter($node); $variableName = $node->getName();
$variableName = substr($node->variableName->getText($node->getFileContents()), 1);
$functionLikeDeclaration = TolerantParserHelpers::getFunctionLikeDeclarationFromParameter($node);
$docBlock = $this->getDocBlock($functionLikeDeclaration); $docBlock = $this->getDocBlock($functionLikeDeclaration);
if ($docBlock !== null) { $parameterDocBlockTag = $this->tryGetDocBlockTagForParameter($docBlock, $variableName);
$parameterDocBlockTag = $this->getDocBlockTagForParameter($docBlock, $variableName);
return $parameterDocBlockTag !== null ? $parameterDocBlockTag->getDescription()->render() : null; return $parameterDocBlockTag !== null ? $parameterDocBlockTag->getDescription()->render() : null;
}
} // For everything else, get the doc block summary corresponding to the current node.
}
// for everything else, get the doc block summary corresponding to the current node.
elseif (!($node instanceof Tolerant\Node\Statement\NamespaceDefinition)) {
$docBlock = $this->getDocBlock($node); $docBlock = $this->getDocBlock($node);
if ($docBlock !== null) { if ($docBlock !== null) {
return $docBlock->getSummary(); return $docBlock->getSummary();
} }
}
return null; return null;
} }
function getDocBlock(Tolerant\Node $node) { /**
// TODO make more efficient * Gets Doc Block with resolved names for a Node
$namespaceDefinition = $node->getNamespaceDefinition(); *
$context = null; * @param Tolerant\Node $node
if ($namespaceDefinition !== null && $namespaceDefinition->name !== null) { * @return DocBlock | null
list($namespaceImportTable,,) = $namespaceDefinition->name->getImportTablesForCurrentScope($namespaceDefinition); */
private function getDocBlock(Tolerant\Node $node)
{
// TODO make more efficient (caching, ensure import table is in right format to begin with)
$docCommentText = $node->getDocCommentText();
if ($docCommentText !== null) {
list($namespaceImportTable,,) = $node->getImportTablesForCurrentScope();
foreach ($namespaceImportTable as $alias=>$name) { foreach ($namespaceImportTable as $alias=>$name) {
$namespaceImportTable[$alias] = (string)$name; $namespaceImportTable[$alias] = (string)$name;
} }
$namespaceDefinition = $node->getNamespaceDefinition();
if ($namespaceDefinition !== null) {
$namespaceName = (string)$namespaceDefinition->name->getNamespacedName(); $namespaceName = (string)$namespaceDefinition->name->getNamespacedName();
$context = new Types\Context($namespaceName, $namespaceImportTable); } else {
$namespaceName = 'global';
} }
$context = new Types\Context($namespaceName, $namespaceImportTable);
$docCommentText = $node->getDocCommentText();
if ($docCommentText !== null) {
try { try {
return $this->docBlockFactory->create($docCommentText, $context); return $this->docBlockFactory->create($docCommentText, $context);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@ -175,52 +162,54 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
public function createDefinitionFromNode($node, string $fqn = null): Definition public function createDefinitionFromNode($node, string $fqn = null): Definition
{ {
$def = new Definition; $def = new Definition;
$def->fqn = $fqn;
// this determines whether the suggestion will show after "new" // Determines whether the suggestion will show after "new"
// TODO name
$def->canBeInstantiated = $node instanceof Tolerant\Node\Statement\ClassDeclaration; $def->canBeInstantiated = $node instanceof Tolerant\Node\Statement\ClassDeclaration;
// Interfaces, classes, traits, namespaces, functions, and global const elements
$def->isGlobal = ( $def->isGlobal = (
$node instanceof Tolerant\Node\Statement\InterfaceDeclaration $node instanceof Tolerant\Node\Statement\InterfaceDeclaration ||
|| $node instanceof Tolerant\Node\Statement\ClassDeclaration $node instanceof Tolerant\Node\Statement\ClassDeclaration ||
|| $node instanceof Tolerant\Node\Statement\TraitDeclaration $node instanceof Tolerant\Node\Statement\TraitDeclaration ||
// TODO namespaces? ($node instanceof Tolerant\Node\Statement\NamespaceDefinition && $node->name !== null) ||
|| $node instanceof Tolerant\Node\Statement\NamespaceDefinition && $node->name !== null
|| $node instanceof Tolerant\Node\Statement\FunctionDeclaration $node instanceof Tolerant\Node\Statement\FunctionDeclaration ||
|| $node instanceof Tolerant\Node\Statement\ConstDeclaration ($node instanceof Tolerant\Node\ConstElement && $node->parent->parent instanceof Tolerant\Node\Statement\ConstDeclaration)
|| $node instanceof Tolerant\Node\ConstElement && $node->parent->parent instanceof Tolerant\Node\Statement\ConstDeclaration
// || $node instanceof Tolerant\Node\ClassConstDeclaration
); );
// Static methods and static property declarations
$def->isStatic = ( $def->isStatic = (
($node instanceof Tolerant\Node\MethodDeclaration && $node->isStatic()) ($node instanceof Tolerant\Node\MethodDeclaration && $node->isStatic()) ||
|| ($node instanceof Tolerant\Node\Expression\Variable &&
($propertyDeclaration = $node->getFirstAncestor(Tolerant\Node\PropertyDeclaration::class)) !== null && (($propertyDeclaration = TolerantParserHelpers::tryGetPropertyDeclaration($node)) !== null
$propertyDeclaration->isStatic()) && $propertyDeclaration->isStatic())
); );
$def->fqn = $fqn;
if ($node instanceof Tolerant\Node\Statement\ClassDeclaration) { if ($node instanceof Tolerant\Node\Statement\ClassDeclaration &&
$def->extends = []; // TODO - don't instantiate array // TODO - this should be bette rrpreented in the parser API
if ($node->classBaseClause !== null && $node->classBaseClause->baseClass !== null) { $node->classBaseClause !== null && $node->classBaseClause->baseClass !== null)
$def->extends[] = (string)$node->classBaseClause->baseClass->getResolvedName(); {
} $def->extends = [(string)$node->classBaseClause->baseClass->getResolvedName()];
// TODO what about class interfaces // TODO - why is this represented as an array?
} else if ($node instanceof Tolerant\Node\Statement\InterfaceDeclaration) { // TODO interface implementations.
} elseif (
$node instanceof Tolerant\Node\Statement\InterfaceDeclaration &&
// TODO - this hould be better represented in the parser API
$node->interfaceBaseClause !== null && $node->interfaceBaseClause->interfaceNameList !== null
) {
$def->extends = []; $def->extends = [];
if ($node->interfaceBaseClause !== null && $node->interfaceBaseClause->interfaceNameList !== null) {
foreach ($node->interfaceBaseClause->interfaceNameList->getValues() as $n) { foreach ($node->interfaceBaseClause->interfaceNameList->getValues() as $n) {
$def->extends[] = (string)$n->getResolvedName(); $def->extends[] = (string)$n->getResolvedName();
} }
} }
}
$def->symbolInformation = TolerantSymbolInformation::fromNode($node, $fqn); $def->symbolInformation = TolerantSymbolInformation::fromNode($node, $fqn);
if ($def->symbolInformation !== null) { if ($def->symbolInformation !== null) {
$def->type = $this->getTypeFromNode($node); //TODO $def->type = $this->getTypeFromNode($node);
$def->declarationLine = $this->getDeclarationLineFromNode($node); $def->declarationLine = $this->getDeclarationLineFromNode($node);
$def->documentation = $this->getDocumentationFromNode($node); $def->documentation = $this->getDocumentationFromNode($node);
} }
@ -237,13 +226,16 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
public function resolveReferenceNodeToDefinition($node) public function resolveReferenceNodeToDefinition($node)
{ {
$parent = $node->parent; $parent = $node->parent;
// 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 Tolerant\Node\Expression\Variable && !($parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression)) { // Ignore variable nodes that are part of ScopedPropertyAccessExpression,
// Resolve $this // as the scoped property access expression node is handled separately.
if ($node instanceof Tolerant\Node\Expression\Variable &&
!($parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression))
{
// Resolve $this to the containing class definition.
if ($node->getName() === '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);
} }
// TODO running through thid for class constants or properties
// 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 = $this->resolveVariableToNode($node); $defNode = $this->resolveVariableToNode($node);
@ -260,7 +252,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
$globalFallback = $this->isConstantFetch($node) || $node->parent instanceof Tolerant\Node\Expression\CallExpression; $globalFallback = TolerantParserHelpers::isConstantFetch($node) || $parent instanceof Tolerant\Node\Expression\CallExpression;
// 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);
} }
@ -273,45 +265,76 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
* @return string|null * @return string|null
*/ */
public function resolveReferenceNodeToFqn($node) { public function resolveReferenceNodeToFqn($node) {
$parent = $node->parent;
// TODO all name tokens should be a part of a node // TODO all name tokens should be a part of a node
if ($node instanceof Tolerant\Node\QualifiedName) { if ($node instanceof Tolerant\Node\QualifiedName) {
// For extends, implements, type hints and classes of classes of static calls use the name directly return $this->resolveQualifiedNameNodeToFqn($node);
$name = $node->getResolvedName(); }
if (($useClause = $node->getFirstAncestor(Tolerant\Node\NamespaceUseGroupClause::class, Tolerant\Node\Statement\NamespaceUseDeclaration::class)) !== null) { else if ($node instanceof Tolerant\Node\Expression\MemberAccessExpression) {
$name = (string)($name ?? Tolerant\ResolvedName::buildName($node->nameParts, $node->getFileContents())); return $this->resolveMemberAccessExpressionNodeToFqn($node);
}
else if (TolerantParserHelpers::isConstantFetch($node)) {
return (string)($node->getNamespacedName());
}
else if (
// A\B::C - constant access expression
$node instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
&& !($node->memberName instanceof Tolerant\Node\Expression\Variable)
) {
return $this->resolveScopedPropertyAccessExpressionNodeToFqn($node);
} else if (
// A\B::$c - static property access expression
$node->parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
) {
return $this->resolveScopedPropertyAccessExpressionNodeToFqn($node->parent);
}
return null;
}
private function resolveQualifiedNameNodeToFqn(Tolerant\Node\QualifiedName $node) {
$parent = $node->parent;
// Add use clause references
if (($useClause = $parent) instanceof Tolerant\Node\NamespaceUseGroupClause
|| $useClause instanceof Tolerant\Node\NamespaceUseClause
) {
$contents = $node->getFileContents();
if ($useClause instanceof Tolerant\Node\NamespaceUseGroupClause) { if ($useClause instanceof Tolerant\Node\NamespaceUseGroupClause) {
$prefix = $useClause->parent->parent->namespaceName; $prefix = $useClause->parent->parent->namespaceName;
if ($prefix === null) { if ($prefix === null) {
return null; return null;
} }
$prefixName = Tolerant\ResolvedName::buildName($prefix->nameParts, $useClause->getFileContents()); $name = Tolerant\ResolvedName::buildName($prefix->nameParts, $contents);
$name = (string)$prefixName . "\\" . $name; $name->addNameParts($node->nameParts, $contents);
$name = (string)$name;
if ($useClause->functionOrConst === null) { if ($useClause->functionOrConst === null) {
$useClause = $node->getFirstAncestor(Tolerant\Node\Statement\NamespaceUseDeclaration::class); $useClause = $node->getFirstAncestor(Tolerant\Node\Statement\NamespaceUseDeclaration::class);
}
}
if ($useClause->functionOrConst !== null && $useClause->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) { if ($useClause->functionOrConst !== null && $useClause->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) {
$name .= '()'; $name .= '()';
} }
} }
else { return $name;
$name = (string)($name ?? (string)$node->getNamespacedName()); } else {
$name = (string) Tolerant\ResolvedName::buildName($node->nameParts, $contents);
if ($useClause->groupClauses === null && $useClause->parent->parent->functionOrConst !== null && $useClause->parent->parent->functionOrConst->kind === Tolerant\TokenKind::FunctionKeyword) {
$name .= '()';
} }
}
return $name;
}
// For extends, implements, type hints and classes of classes of static calls use the name directly
$name = (string) ($node->getResolvedName() ?? $node->getNamespacedName());
if ($node->parent instanceof Tolerant\Node\Expression\CallExpression) { if ($node->parent instanceof Tolerant\Node\Expression\CallExpression) {
$name .= '()'; $name .= '()';
} }
// does this work for function calls?
return $name; return $name;
} }
else if ( private function resolveMemberAccessExpressionNodeToFqn(Tolerant\Node\Expression\MemberAccessExpression $access) {
(($access = $node) instanceof Tolerant\Node\Expression\MemberAccessExpression)
) {
if ($access->memberName instanceof Tolerant\Node\Expression) { 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;
@ -340,27 +363,22 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
|| $varType instanceof Types\Self_ || $varType instanceof Types\Self_
) { ) {
// $this/static/self is resolved to the containing class // $this/static/self is resolved to the containing class
$classFqn = self::getContainingClassFqn($node); $classFqn = self::getContainingClassFqn($access);
} else if (!($varType instanceof Types\Object_) || $varType->getFqsen() === null) { } else if (!($varType instanceof Types\Object_) || $varType->getFqsen() === null) {
// Left-hand expression could not be resolved to a class // Left-hand expression could not be resolved to a class
return null; return null;
} else { } else {
$classFqn = substr((string)$varType->getFqsen(), 1); $classFqn = substr((string)$varType->getFqsen(), 1);
// TODO
// $classFqn = $node->getNamespaceDefinition()->name->getNamespacedName() . (string)$varType->getFqsen();
// var_dump($classFqn);
} }
$memberSuffix = '->' . (string)($access->memberName->getText() ?? $access->memberName->getText($node->getFileContents())); $memberSuffix = '->' . (string)($access->memberName->getText() ?? $access->memberName->getText($access->getFileContents()));
if ($node->parent instanceof Tolerant\Node\Expression\CallExpression) { if ($access->parent instanceof Tolerant\Node\Expression\CallExpression) {
// TODO - this is redundant
$memberSuffix .= '()'; $memberSuffix .= '()';
} }
// Find the right class that implements the member // Find the right class that implements the member
$implementorFqns = [$classFqn]; $implementorFqns = [$classFqn];
while ($implementorFqn = array_shift($implementorFqns)) { while ($implementorFqn = array_shift($implementorFqns)) {
// var_dump($implementorFqn . $memberSuffix);
// If the member FQN exists, return it // If the member FQN exists, return it
if ($this->index->getDefinition($implementorFqn . $memberSuffix)) { if ($this->index->getDefinition($implementorFqn . $memberSuffix)) {
@ -380,33 +398,14 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
} }
// var_dump("SUFFIX");
// var_dump($classFqn);
// var_dump($memberSuffix);
return $classFqn . $memberSuffix; return $classFqn . $memberSuffix;
} }
else if ($parent instanceof Tolerant\Node\Expression\CallExpression && $node instanceof Tolerant\Node\QualifiedName) {
if ($parent->name instanceof Node\Expr) { private function resolveScopedPropertyAccessExpressionNodeToFqn(Tolerant\Node\Expression\ScopedPropertyAccessExpression $scoped) {
return null;
}
$name = (string)($parent->getNamespacedName());
}
else if ($this->isConstantFetch($node)) {
$name = (string)($node->getNamespacedName());
}
else if (
($scoped = $node) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|| ($scoped = $node->parent) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|| ($node->parent instanceof Tolerant\Node\Expression\CallExpression && ($scoped = $node->parent->callableExpression) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression)
) {
// if ($scoped->memberName instanceof Tolerant\Node\Expression) {
// Cannot get definition of dynamic names
// return null;
// }
$className = $scoped->scopeResolutionQualifier->getText(); $className = $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 = $node->getFirstAncestor(Tolerant\Node\Statement\ClassDeclaration::class); $classNode = $scoped->getFirstAncestor(Tolerant\Node\Statement\ClassDeclaration::class);
if ($classNode === null) { if ($classNode === null) {
return null; return null;
} }
@ -425,53 +424,13 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
if ($scoped->memberName instanceof Tolerant\Node\Expression\Variable) { if ($scoped->memberName instanceof Tolerant\Node\Expression\Variable) {
$name = (string)$className . '::$' . $scoped->memberName->getName(); $name = (string)$className . '::$' . $scoped->memberName->getName();
} else { } else {
$name = (string)$className . '::' . $scoped->memberName->getText($node->getFileContents()); $name = (string)$className . '::' . $scoped->memberName->getText($scoped->getFileContents());
} }
if ($scoped->parent instanceof Tolerant\Node\Expression\CallExpression) { if ($scoped->parent instanceof Tolerant\Node\Expression\CallExpression) {
$name .= '()'; $name .= '()';
} }
return $name; return $name;
} }
else {
return null;
}
if (!isset($name)) {
return null;
}
if (
$node instanceof Tolerant\Node\Expression\CallExpression
) {
$name .= '()';
}
return $name;
}
public static function isConstantFetch(Tolerant\Node $node) : bool {
return
(
$node instanceof Tolerant\Node\QualifiedName &&
(
// $node->parent instanceof Tolerant\Node\Statement\ExpressionStatement ||
$node->parent instanceof Tolerant\Node\Expression ||
$node->parent instanceof Tolerant\Node\DelimitedList\ExpressionList ||
$node->parent instanceof Tolerant\Node\ArrayElement ||
($node->parent instanceof Tolerant\Node\Parameter && $node->parent->default === $node) ||
$node->parent instanceof Tolerant\Node\StatementNode ||
$node->parent instanceof Tolerant\Node\CaseStatementNode
) &&
!(
$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
@ -511,7 +470,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
// 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 (self::isFunctionLike($n)) { if (TolerantParserHelpers::isFunctionLike($n)) {
if ($n->parameters !== null) { if ($n->parameters !== null) {
foreach ($n->parameters->getElements() as $param) { foreach ($n->parameters->getElements() as $param) {
@ -550,26 +509,11 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
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
);
}
public 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.
* *
* @param \PhpParser\Node\Expr $expr * @param Tolerant\Node\Expression $expr
* @return \phpDocumentor\Reflection\Type * @return \phpDocumentor\Reflection\Type
*/ */
public function resolveExpressionNodeToType($expr): Type public function resolveExpressionNodeToType($expr): Type
@ -580,7 +524,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return new Types\Mixed; return new Types\Mixed;
} }
if ($expr instanceof Tolerant\Node\Expression\ParenthesizedExpression) { while ($expr instanceof Tolerant\Node\Expression\ParenthesizedExpression) {
$expr = $expr->expression; $expr = $expr->expression;
} }
@ -622,7 +566,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return new Types\Boolean; return new Types\Boolean;
} }
if ($this->isConstantFetch($expr)) { if (TolerantParserHelpers::isConstantFetch($expr)) {
// Resolve constant // Resolve constant
$fqn = (string)$expr->getNamespacedName(); $fqn = (string)$expr->getNamespacedName();
$def = $this->index->getDefinition($fqn, true); $def = $this->index->getDefinition($fqn, true);
@ -636,10 +580,8 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
$var = $access->dereferencableExpression; $var = $access->dereferencableExpression;
// var_dump("HERE!!!");
// Resolve object // Resolve object
$objType = $this->resolveExpressionNodeToType($var); $objType = $this->resolveExpressionNodeToType($var);
// var_dump($objType);
if (!($objType instanceof Types\Compound)) { if (!($objType instanceof Types\Compound)) {
$objType = new Types\Compound([$objType]); $objType = new Types\Compound([$objType]);
} }
@ -653,16 +595,12 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return new Types\Mixed; return new Types\Mixed;
} else { } else {
$classFqn = substr((string)$t->getFqsen(), 1); $classFqn = substr((string)$t->getFqsen(), 1);
// var_dump($classFqn);
} }
$fqn = $classFqn . '->' . $access->memberName->getText($expr->getFileContents()); $fqn = $classFqn . '->' . $access->memberName->getText($expr->getFileContents());
if ($expr->parent instanceof Tolerant\Node\Expression\CallExpression) { if ($expr->parent instanceof Tolerant\Node\Expression\CallExpression) {
$fqn .= '()'; $fqn .= '()';
} }
// var_dump($fqn);
// var_dump($fqn);
$def = $this->index->getDefinition($fqn); $def = $this->index->getDefinition($fqn);
// var_dump($def);
if ($def !== null) { if ($def !== null) {
return $def->type; return $def->type;
} }
@ -672,19 +610,16 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
($scopedAccess = $expr) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ($scopedAccess = $expr) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
) { ) {
$classType = $this->resolveClassNameToType($scopedAccess->scopeResolutionQualifier); $classType = $this->resolveClassNameToType($scopedAccess->scopeResolutionQualifier);
// var_dump($classType);
if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null /*|| $expr->name instanceof Tolerant\Node\Expression*/) { 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 Tolerant\Node\Expression\ScopedPropertyAccessExpression && $expr->memberName instanceof Tolerant\Node\Expression\Variable) {
// $fqn .= '$';
// }
$fqn .= $scopedAccess->memberName->getText() ?? $scopedAccess->memberName->getText($expr->getFileContents()); // TODO is there a cleaner way to do this? $fqn .= $scopedAccess->memberName->getText() ?? $scopedAccess->memberName->getText($expr->getFileContents()); // TODO is there a cleaner way to do this?
if ($expr->parent instanceof Tolerant\Node\Expression\CallExpression) { if ($expr->parent instanceof Tolerant\Node\Expression\CallExpression) {
$fqn .= '()'; $fqn .= '()';
} }
// var_dump($fqn);
$def = $this->index->getDefinition($fqn); $def = $this->index->getDefinition($fqn);
if ($def === null) { if ($def === null) {
return new Types\Mixed; return new Types\Mixed;
@ -722,7 +657,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
]); ]);
} }
if ( if (
$this->isBooleanExpression($expr) TolerantParserHelpers::isBooleanExpression($expr)
|| ($expr instanceof Tolerant\Node\Expression\CastExpression && $expr->castType->kind === Tolerant\TokenKind::BoolCastToken) || ($expr instanceof Tolerant\Node\Expression\CastExpression && $expr->castType->kind === Tolerant\TokenKind::BoolCastToken)
|| ($expr instanceof Tolerant\Node\Expression\UnaryOpExpression && $expr->operator->kind === Tolerant\TokenKind::ExclamationToken) || ($expr instanceof Tolerant\Node\Expression\UnaryOpExpression && $expr->operator->kind === Tolerant\TokenKind::ExclamationToken)
@ -819,9 +754,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return new Types\Array_($valueType, $keyType); return new Types\Array_($valueType, $keyType);
} }
if ($expr instanceof Tolerant\Node\Expression\SubscriptExpression) { if ($expr instanceof Tolerant\Node\Expression\SubscriptExpression) {
// var_dump("SUBSCRIPT");
$varType = $this->resolveExpressionNodeToType($expr->postfixExpression); $varType = $this->resolveExpressionNodeToType($expr->postfixExpression);
// var_dump($varType);
if (!($varType instanceof Types\Array_)) { if (!($varType instanceof Types\Array_)) {
return new Types\Mixed; return new Types\Mixed;
} }
@ -834,31 +767,6 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
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
@ -921,16 +829,14 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
if ($node instanceof Tolerant\Node\Parameter) { if ($node instanceof Tolerant\Node\Parameter) {
// Parameters // Parameters
// Get the doc block for the the function call // Get the doc block for the the function call
$functionLikeDeclaration = $this->getFunctionLikeDeclarationFromParameter($node); $functionLikeDeclaration = TolerantParserHelpers::getFunctionLikeDeclarationFromParameter($node);
$variableName = $node->variableName->getText($node->getFileContents()); $variableName = $node->variableName->getText($node->getFileContents());
$docBlock = $this->getDocBlock($functionLikeDeclaration); $docBlock = $this->getDocBlock($functionLikeDeclaration);
if ($docBlock !== null) { $parameterDocBlockTag = $this->tryGetDocBlockTagForParameter($docBlock, $variableName);
$parameterDocBlockTag = $this->getDocBlockTagForParameter($docBlock, $variableName);
if ($parameterDocBlockTag !== null && $parameterDocBlockTag->getType() !== null) { if ($parameterDocBlockTag !== null && $parameterDocBlockTag->getType() !== null) {
return $parameterDocBlockTag->getType(); return $parameterDocBlockTag->getType();
} }
}
if ($node->typeDeclaration !== null) { if ($node->typeDeclaration !== null) {
// Use PHP7 return type hint // Use PHP7 return type hint
@ -952,7 +858,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
return $type ?? new Types\Mixed; return $type ?? new Types\Mixed;
} }
// for functions and methods, get the return type [first from doc block, then from return type] // for functions and methods, get the return type [first from doc block, then from return type]
if ($this->isFunctionLike($node)) { if (TolerantParserHelpers::isFunctionLike($node)) {
// Functions/methods // Functions/methods
$docBlock = $this->getDocBlock($node); $docBlock = $this->getDocBlock($node);
if ( if (
@ -976,14 +882,14 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
} }
// for variables / assignments, get the documented type the assignment resolves to. // for variables / assignments, get the documented type the assignment resolves to.
if ($node instanceof Tolerant\Node\Expression\Variable) { if ($node->parent instanceof Tolerant\Node\Expression\AssignmentExpression) {
$node = $node->parent; $node = $node->parent;
} }
if ( if (
($declarationNode = $node->getFirstAncestor( ($declarationNode =
Tolerant\Node\PropertyDeclaration::class, TolerantParserHelpers::tryGetPropertyDeclaration($node) ??
Tolerant\Node\Statement\ConstDeclaration::class, TolerantParserHelpers::tryGetConstOrClassConstDeclaration($node)
Tolerant\Node\ClassConstDeclaration::class)) !== null || ) !== null ||
$node instanceof Tolerant\Node\Expression\AssignmentExpression) $node instanceof Tolerant\Node\Expression\AssignmentExpression)
{ {
$declarationNode = $declarationNode ?? $node; $declarationNode = $declarationNode ?? $node;
@ -995,8 +901,6 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
&& !empty($varTags = $docBlock->getTagsByName('var')) && !empty($varTags = $docBlock->getTagsByName('var'))
&& ($type = $varTags[0]->getType()) && ($type = $varTags[0]->getType())
) { ) {
// var_dump("BOOYAH");
// var_dump($type);
return $type; return $type;
} }
// Resolve the expression // Resolve the expression
@ -1018,20 +922,6 @@ 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() === \ltrim($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
@ -1059,14 +949,16 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
// INPUT OUTPUT: // INPUT OUTPUT:
// namespace A\B; A\B // namespace A\B; A\B
else if ($node instanceof Tolerant\Node\Statement\NamespaceDefinition && $node->name instanceof Tolerant\Node\QualifiedName) { else if ($node instanceof Tolerant\Node\Statement\NamespaceDefinition && $node->name instanceof Tolerant\Node\QualifiedName) {
return (string) Tolerant\ResolvedName::buildName($node->name->nameParts, $node->getFileContents()); $name = (string) Tolerant\ResolvedName::buildName($node->name->nameParts, $node->getFileContents());
return \count($name) > 0 ? $name : null;
} }
// INPUT OUTPUT: // INPUT OUTPUT:
// namespace A\B; // namespace A\B;
// function a(); A\B\a(); // function a(); A\B\a();
else if ($node instanceof Tolerant\Node\Statement\FunctionDeclaration) { else if ($node instanceof Tolerant\Node\Statement\FunctionDeclaration) {
// Function: use functionName() as the name // Function: use functionName() as the name
return (string)$node->getNamespacedName() . '()'; $name = (string)$node->getNamespacedName();
return \count($name) > 0 ? $name . '()' : null;
} }
// INPUT OUTPUT // INPUT OUTPUT
// namespace A\B; // namespace A\B;
@ -1100,25 +992,25 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
// $a = 4, $b = 4 A\B\C->$a, A\B\C->$b // $a = 4, $b = 4 A\B\C->$a, A\B\C->$b
// } // }
else if ( else if (
$node instanceof Tolerant\Node\Expression\Variable && ($propertyDeclaration = TolerantParserHelpers::tryGetPropertyDeclaration($node)) !== null &&
($propertyDeclaration = $node->getFirstAncestor(Tolerant\Node\PropertyDeclaration::class)) !== null &&
($classDeclaration = ($classDeclaration =
$node->getFirstAncestor( $node->getFirstAncestor(
Tolerant\Node\Statement\ObjectCreationExpression::class, Tolerant\Node\Expression\ObjectCreationExpression::class,
Tolerant\Node\Statement\ClassDeclaration::class, Tolerant\Node\Statement\ClassDeclaration::class,
Tolerant\Node\Statement\InterfaceDeclaration::class, Tolerant\Node\Statement\InterfaceDeclaration::class,
Tolerant\Node\Statement\TraitDeclaration::class Tolerant\Node\Statement\TraitDeclaration::class
) )
) !== null && !($classDeclaration instanceof Tolerant\Node\Statement\ObjectCreationExpression)) ) !== null && isset($classDeclaration->name))
{ {
$name = $node->getName();
if ($propertyDeclaration->isStatic()) { if ($propertyDeclaration->isStatic()) {
// Static Property: use ClassName::$propertyName as name // Static Property: use ClassName::$propertyName as name
return (string)$classDeclaration->getNamespacedName() . '::$' . (string)$node->getName(); return (string)$classDeclaration->getNamespacedName() . '::$' . $name;
} elseif (($name = $node->getName()) !== null) { }
// Instance Property: use ClassName->propertyName as name // Instance Property: use ClassName->propertyName as name
return (string)$classDeclaration->getNamespacedName() . '->' . $name; return (string)$classDeclaration->getNamespacedName() . '->' . $name;
} }
}
// INPUT OUTPUT // INPUT OUTPUT
// namespace A\B; // namespace A\B;
@ -1126,24 +1018,43 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
// class C { // class C {
// const $a, $b = 4 A\B\C::$a(), A\B\C::$b // const $a, $b = 4 A\B\C::$a(), A\B\C::$b
// } // }
else if ($node instanceof Tolerant\Node\ConstElement) { else if (($constDeclaration = TolerantParserHelpers::tryGetConstOrClassConstDeclaration($node)) !== null) {
$constDeclaration = $node->getFirstAncestor(Tolerant\Node\Statement\ConstDeclaration::class, Tolerant\Node\ClassConstDeclaration::class);
if ($constDeclaration instanceof Tolerant\Node\Statement\ConstDeclaration) { if ($constDeclaration instanceof Tolerant\Node\Statement\ConstDeclaration) {
// Basic constant: use CONSTANT_NAME as name // Basic constant: use CONSTANT_NAME as name
return (string)$node->getNamespacedName(); return (string)$node->getNamespacedName();
} }
if ($constDeclaration instanceof Tolerant\Node\ClassConstDeclaration) {
// Class constant: use ClassName::CONSTANT_NAME as name // Class constant: use ClassName::CONSTANT_NAME as name
$classDeclaration = $constDeclaration->getFirstAncestor( $classDeclaration = $constDeclaration->getFirstAncestor(
Tolerant\Node\Expression\ObjectCreationExpression::class,
Tolerant\Node\Statement\ClassDeclaration::class, Tolerant\Node\Statement\ClassDeclaration::class,
Tolerant\Node\Statement\InterfaceDeclaration::class, Tolerant\Node\Statement\InterfaceDeclaration::class,
Tolerant\Node\Statement\TraitDeclaration::class Tolerant\Node\Statement\TraitDeclaration::class
); );
if (!isset($classDeclaration->name)) { if (!isset($classDeclaration->name)) {
return null; return null;
} }
return (string)$classDeclaration->getNamespacedName() . '::' . $node->getName(); return (string)$classDeclaration->getNamespacedName() . '::' . $node->getName();
} }
return null;
}
/**
* @param DocBlock | null $docBlock
* @param $variableName
* @return DocBlock\Tags\Param | null
*/
private function tryGetDocBlockTagForParameter($docBlock, $variableName) {
if ($docBlock === null) {
return null;
}
$tags = $docBlock->getTagsByName('param');
foreach ($tags as $tag) {
if ($tag->getVariableName() === \ltrim($variableName, "$")) {
return $tag;
}
} }
} }
} }

View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace LanguageServer;
use Microsoft\PhpParser as Tolerant;
class TolerantParserHelpers {
public static function isConstantFetch(Tolerant\Node $node) : bool {
return
(
$node instanceof Tolerant\Node\QualifiedName &&
(
// $node->parent instanceof Tolerant\Node\Statement\ExpressionStatement ||
$node->parent instanceof Tolerant\Node\Expression ||
$node->parent instanceof Tolerant\Node\DelimitedList\ExpressionList ||
$node->parent instanceof Tolerant\Node\ArrayElement ||
($node->parent instanceof Tolerant\Node\Parameter && $node->parent->default === $node) ||
$node->parent instanceof Tolerant\Node\StatementNode ||
$node->parent instanceof Tolerant\Node\CaseStatementNode
) &&
!(
$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
)
));
}
public static function getFunctionLikeDeclarationFromParameter(Tolerant\Node\Parameter $node) {
return $node->parent->parent;
}
public static function isFunctionLike(Tolerant\Node $node) {
return
$node instanceof Tolerant\Node\Statement\FunctionDeclaration ||
$node instanceof Tolerant\Node\MethodDeclaration ||
$node instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression;
}
public static 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;
}
/**
* Tries to get the parent property declaration given a Node
* @param Tolerant\Node $node
* @return Tolerant\Node\PropertyDeclaration | null $node
*/
public static function tryGetPropertyDeclaration(Tolerant\Node $node) {
if ($node instanceof Tolerant\Node\Expression\Variable &&
(($propertyDeclaration = $node->parent->parent) instanceof Tolerant\Node\PropertyDeclaration ||
($propertyDeclaration = $propertyDeclaration->parent) instanceof Tolerant\Node\PropertyDeclaration)
) {
return $propertyDeclaration;
}
return null;
}
/**
* Tries to get the parent ConstDeclaration or ClassConstDeclaration given a Node
* @param Tolerant\Node $node
* @return Tolerant\Node\Statement\ConstDeclaration | Tolerant\Node\ClassConstDeclaration | null $node
*/
public static function tryGetConstOrClassConstDeclaration(Tolerant\Node $node) {
if (
$node instanceof Tolerant\Node\ConstElement && (
($constDeclaration = $node->parent->parent) instanceof Tolerant\Node\ClassConstDeclaration ||
$constDeclaration instanceof Tolerant\Node\Statement\ConstDeclaration )
) {
return $constDeclaration;
}
return null;
}
}

View File

@ -127,7 +127,7 @@ class TolerantTreeAnalyzer implements TreeAnalyzerInterface {
// Namespaced constant access and function calls also need to register a reference // Namespaced constant access and function calls also need to register a reference
// to the global version because PHP falls back to global at runtime // to the global version because PHP falls back to global at runtime
// http://php.net/manual/en/language.namespaces.fallback.php // http://php.net/manual/en/language.namespaces.fallback.php
if (TolerantDefinitionResolver::isConstantFetch($node) || if (TolerantParserHelpers::isConstantFetch($node) ||
($parent instanceof Tolerant\Node\Expression\CallExpression ($parent instanceof Tolerant\Node\Expression\CallExpression
&& !( && !(
$parent->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression || $parent->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||

View File

@ -0,0 +1,11 @@
<?php
namespace TestNamespace;
$a = new A;
echo $a->a;
class A {
public $a = 3;
}

View File

@ -0,0 +1,19 @@
<?php
namespace TestNamespace;
use SomeNamespace\Goo;
class TestClass
{
public $testProperty;
public function testMethod($testParameter)
{
$testVariable = 123;
if (empty($testParameter)) {
echo 'Empty';
}
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace MyNamespace;
class Mbstring
{
const MB_CASE_FOLD = PHP_INT_MAX;
}

View File

@ -0,0 +1,11 @@
<?php
namespace MyNamespace;
class ParseErrorsTest {
public function setUp()
{
$a = new class($this->args) { };
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace MyNamespace;
class ParseErrorsTest
{
public function setAccount(AccountInterface $account)
{
// If the passed account is already proxied, use the actual account instead
// to prevent loops.
if ($account instanceof A) {
$account = $account->getAccount();
}
}
}

View File

@ -0,0 +1,7 @@
<?php
class A {
function b() {
$a instanceof static;
}
}

View File

@ -0,0 +1,4 @@
<?php
namespace {
}

View File

@ -0,0 +1,6 @@
<?php
declare(strict_types = 1);
namespace LanguageServer\Tests\Utils;
use function LanguageServer\{pathToUri, uriToPath};

View File

@ -0,0 +1,8 @@
<?php
class A {
static $a = "hello";
}
echo A::$a;
$a = new

View File

@ -0,0 +1,13 @@
<?php
class TestClass implements TestInterface {
/**
* Lorem excepteur officia sit anim velit veniam enim.
*
* @var TestClass[]
*/
public static $testProperty;
}
echo TestClass::$testProperty;
echo TestClass::$staticTestProperty[123]->testProperty;