first pass at completion provider (work in progress)
parent
ed6469219a
commit
76c8536e91
|
@ -13,6 +13,7 @@ use LanguageServer\Protocol\{
|
||||||
CompletionItem,
|
CompletionItem,
|
||||||
CompletionItemKind
|
CompletionItemKind
|
||||||
};
|
};
|
||||||
|
use Microsoft\PhpParser as Tolerant;
|
||||||
|
|
||||||
class CompletionProvider
|
class CompletionProvider
|
||||||
{
|
{
|
||||||
|
@ -123,44 +124,71 @@ class CompletionProvider
|
||||||
{
|
{
|
||||||
// This can be made much more performant if the tree follows specific invariants.
|
// This can be made much more performant if the tree follows specific invariants.
|
||||||
$node = $doc->getNodeAtPosition($pos);
|
$node = $doc->getNodeAtPosition($pos);
|
||||||
|
|
||||||
|
|
||||||
if ($node instanceof Node\Expr\Error) {
|
if($node !== null && ($offset = $pos->toOffset($node->getFileContents())) > $node->getEndPosition() &&
|
||||||
$node = $node->getAttribute('parentNode');
|
$node->parent->getLastChild() instanceof Tolerant\MissingToken) {
|
||||||
|
$node = $node->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = new CompletionList;
|
$list = new CompletionList;
|
||||||
$list->isIncomplete = true;
|
$list->isIncomplete = true;
|
||||||
|
|
||||||
// A non-free node means we do NOT suggest global symbols
|
if ($node instanceof Tolerant\Node\Expression\Variable &&
|
||||||
if (
|
$node->parent instanceof Tolerant\Node\Expression\ObjectCreationExpression &&
|
||||||
$node instanceof Node\Expr\MethodCall
|
$node->name instanceof Tolerant\MissingToken
|
||||||
|| $node instanceof Node\Expr\PropertyFetch
|
|
||||||
|| $node instanceof Node\Expr\StaticCall
|
|
||||||
|| $node instanceof Node\Expr\StaticPropertyFetch
|
|
||||||
|| $node instanceof Node\Expr\ClassConstFetch
|
|
||||||
) {
|
) {
|
||||||
// If the name is an Error node, just filter by the class
|
$node = $node->parent;
|
||||||
if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
|
}
|
||||||
// For instances, resolve the variable type
|
|
||||||
$prefixes = FqnUtilities::getFqnsFromType(
|
if ($node === null || $node instanceof Tolerant\Node\Statement\InlineHtml || $pos == new Position(0, 0)) {
|
||||||
$this->definitionResolver->resolveExpressionNodeToType($node->var)
|
$item = new CompletionItem('<?php', CompletionItemKind::KEYWORD);
|
||||||
|
$item->textEdit = new TextEdit(
|
||||||
|
new Range($pos, $pos),
|
||||||
|
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), '<?php')
|
||||||
|
);
|
||||||
|
$list->items[] = $item;
|
||||||
|
}
|
||||||
|
// VARIABLES
|
||||||
|
elseif (
|
||||||
|
$node instanceof Tolerant\Node\Expression\Variable &&
|
||||||
|
!(
|
||||||
|
$node->parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression &&
|
||||||
|
$node->parent->memberName === $node)
|
||||||
|
) {
|
||||||
|
// Find variables, parameters and use statements in the scope
|
||||||
|
// If there was only a $ typed, $node will be instanceof Node\Error
|
||||||
|
$namePrefix = $node->getName() ?? '';
|
||||||
|
foreach ($this->suggestVariablesAtNode($node, $namePrefix) as $var) {
|
||||||
|
$item = new CompletionItem;
|
||||||
|
$item->kind = CompletionItemKind::VARIABLE;
|
||||||
|
$item->label = '$' . $var->getName();
|
||||||
|
$item->documentation = $this->definitionResolver->getDocumentationFromNode($var);
|
||||||
|
$item->detail = (string)$this->definitionResolver->getTypeFromNode($var);
|
||||||
|
$item->textEdit = new TextEdit(
|
||||||
|
new Range($pos, $pos),
|
||||||
|
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), $item->label)
|
||||||
);
|
);
|
||||||
} else {
|
$list->items[] = $item;
|
||||||
// Static member reference
|
|
||||||
$prefixes = [$node->class instanceof Node\Name ? (string)$node->class : ''];
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MEMBER ACCESS EXPRESSIONS
|
||||||
|
// $a->c#
|
||||||
|
// $a->#
|
||||||
|
elseif ($node instanceof Tolerant\Node\Expression\MemberAccessExpression) {
|
||||||
|
$prefixes = FqnUtilities::getFqnsFromType(
|
||||||
|
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
||||||
|
);
|
||||||
$prefixes = $this->expandParentFqns($prefixes);
|
$prefixes = $this->expandParentFqns($prefixes);
|
||||||
// If we are just filtering by the class, add the appropiate operator to the prefix
|
|
||||||
// to filter the type of symbol
|
|
||||||
foreach ($prefixes as &$prefix) {
|
foreach ($prefixes as &$prefix) {
|
||||||
if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
|
$prefix .= '->';
|
||||||
$prefix .= '->';
|
if ($node->memberName !== null && $node->memberName instanceof Tolerant\Token) {
|
||||||
} else if ($node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\ClassConstFetch) {
|
$prefix .= $node->memberName->getText($node->getFileContents());
|
||||||
$prefix .= '::';
|
|
||||||
} else if ($node instanceof Node\Expr\StaticPropertyFetch) {
|
|
||||||
$prefix .= '::$';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($prefix);
|
unset($prefix);
|
||||||
|
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
|
@ -170,125 +198,126 @@ class CompletionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
}
|
||||||
// A ConstFetch means any static reference, like a class, interface, etc. or keyword
|
|
||||||
($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch)
|
// SCOPED PROPERTY ACCESS EXPRESSIONS
|
||||||
|| $node instanceof Node\Expr\New_
|
// A\B\C::$a#
|
||||||
|
// A\B\C::#
|
||||||
|
// A\B\C::$#
|
||||||
|
// A\B\C::foo#
|
||||||
|
// TODO: $a::#
|
||||||
|
elseif (
|
||||||
|
($scoped = $node->parent) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
|
||||||
|
($scoped = $node) instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression
|
||||||
) {
|
) {
|
||||||
$prefix = '';
|
$prefixes = FqnUtilities::getFqnsFromType(
|
||||||
$prefixLen = 0;
|
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
||||||
if ($node instanceof Node\Name) {
|
);
|
||||||
$isFullyQualified = $node->isFullyQualified();
|
|
||||||
$prefix = (string)$node;
|
$prefixes = $this->expandParentFqns($prefixes);
|
||||||
$prefixLen = strlen($prefix);
|
|
||||||
$namespacedPrefix = (string)$node->getAttribute('namespacedName');
|
foreach ($prefixes as &$prefix) {
|
||||||
$namespacedPrefixLen = strlen($prefix);
|
$prefix .= '::';
|
||||||
}
|
}
|
||||||
// Find closest namespace
|
|
||||||
$namespace = getClosestNode($node, Node\Stmt\Namespace_::class);
|
unset($prefix);
|
||||||
/** Map from alias to Definition */
|
|
||||||
$aliasedDefs = [];
|
$memberName = $scoped->memberName->getText($scoped->getFileContents());
|
||||||
if ($namespace) {
|
|
||||||
foreach ($namespace->stmts as $stmt) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
if ($stmt instanceof Node\Stmt\Use_ || $stmt instanceof Node\Stmt\GroupUse) {
|
foreach ($prefixes as $prefix) {
|
||||||
foreach ($stmt->uses as $use) {
|
if (substr(strtolower($fqn), 0, strlen($prefix)) === strtolower($prefix) && !$def->isGlobal) {
|
||||||
// Get the definition for the used namespace, class-like, function or constant
|
if (empty($memberName) || strpos(substr(strtolower($fqn), 0), strtolower($memberName)) !== false) {
|
||||||
// And save it under the alias
|
$list->items[] = CompletionItem::fromDefinition($def);
|
||||||
$fqn = (string)Node\Name::concat($stmt->prefix ?? null, $use->name);
|
|
||||||
if ($def = $this->index->getDefinition($fqn)) {
|
|
||||||
$aliasedDefs[$use->alias] = $def;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Use statements are always the first statements in a namespace
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there is a prefix that does not start with a slash, suggest `use`d symbols
|
} elseif (TolerantParserHelpers::isConstantFetch($node) ||
|
||||||
if ($prefix && !$isFullyQualified) {
|
($creation = $node->parent) instanceof Tolerant\Node\Expression\ObjectCreationExpression ||
|
||||||
// Suggest symbols that have been `use`d
|
(($creation = $node) instanceof Tolerant\Node\Expression\ObjectCreationExpression)) {
|
||||||
// Search the aliases for the typed-in name
|
|
||||||
foreach ($aliasedDefs as $alias => $def) {
|
$class = isset($creation) ? $creation->classTypeDesignator : $node;
|
||||||
if (substr($alias, 0, $prefixLen) === $prefix) {
|
|
||||||
$list->items[] = CompletionItem::fromDefinition($def);
|
$prefix = $class instanceof Tolerant\Node\QualifiedName
|
||||||
}
|
? (string)Tolerant\ResolvedName::buildName($class->nameParts, $class->getFileContents())
|
||||||
}
|
: $class->getText($node->getFileContents());
|
||||||
|
|
||||||
|
$namespaceDefinition = $node->getNamespaceDefinition();
|
||||||
|
|
||||||
|
list($namespaceImportTable,,) = $node->getImportTablesForCurrentScope();
|
||||||
|
foreach ($namespaceImportTable as $alias=>$name) {
|
||||||
|
$namespaceImportTable[$alias] = (string)$name;
|
||||||
}
|
}
|
||||||
// Additionally, suggest global symbols that either
|
|
||||||
// - start with the current namespace + prefix, if the Name node is not fully qualified
|
|
||||||
// - start with just the prefix, if the Name node is fully qualified
|
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
if (
|
if (
|
||||||
$def->isGlobal // exclude methods, properties etc.
|
($def->canBeInstantiated || ($def->isGlobal && !isset($creation))) && (empty($prefix) || strpos($fqn, $prefix) !== false)
|
||||||
&& (
|
|
||||||
!$prefix
|
|
||||||
|| (
|
|
||||||
((!$namespace || $isFullyQualified) && substr($fqn, 0, $prefixLen) === $prefix)
|
|
||||||
|| (
|
|
||||||
$namespace
|
|
||||||
&& !$isFullyQualified
|
|
||||||
&& substr($fqn, 0, $namespacedPrefixLen) === $namespacedPrefix
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// Only suggest classes for `new`
|
|
||||||
&& (!($node instanceof Node\Expr\New_) || $def->canBeInstantiated)
|
|
||||||
) {
|
) {
|
||||||
$item = CompletionItem::fromDefinition($def);
|
if ($namespaceDefinition !== null && $namespaceDefinition->name !== null) {
|
||||||
// Find the shortest name to reference the symbol
|
$namespacePrefix = (string)Tolerant\ResolvedName::buildName($namespaceDefinition->name->nameParts, $node->getFileContents());
|
||||||
if ($namespace && ($alias = array_search($def, $aliasedDefs, true)) !== false) {
|
|
||||||
// $alias is the name under which this definition is aliased in the current namespace
|
$isAliased = false;
|
||||||
$item->insertText = $alias;
|
|
||||||
} else if ($namespace && !($prefix && $isFullyQualified)) {
|
$isNotFullyQualified = !($class instanceof Tolerant\Node\QualifiedName) || !$class->isFullyQualifiedName();
|
||||||
// Insert the global FQN with trailing backslash
|
if ($isNotFullyQualified) {
|
||||||
$item->insertText = '\\' . $fqn;
|
foreach ($namespaceImportTable as $alias => $name) {
|
||||||
} else {
|
if (strpos($fqn, $name) === 0) {
|
||||||
// Insert the FQN without trailing backlash
|
$fqn = $alias;
|
||||||
$item->insertText = $fqn;
|
$isAliased = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!$isNotFullyQualified && ((strpos($fqn, $prefix) === 0) || strpos($fqn, $namespacePrefix . "\\" . $prefix) === 0)) {
|
||||||
|
$fqn = $fqn;
|
||||||
|
}
|
||||||
|
elseif (!$isAliased && !array_search($fqn, array_values($namespaceImportTable))) {
|
||||||
|
if (empty($prefix)) {
|
||||||
|
$fqn = '\\' . $fqn;
|
||||||
|
} elseif (strpos($fqn, $namespacePrefix . "\\" . $prefix) === 0) {
|
||||||
|
$fqn = substr($fqn, strlen($namespacePrefix) + 1);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} elseif (!$isAliased) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} elseif (strpos($fqn, $prefix) === 0 && $class->isFullyQualifiedName()) {
|
||||||
|
$fqn = '\\' . $fqn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$item = CompletionItem::fromDefinition($def);
|
||||||
|
|
||||||
|
$item->insertText = $fqn;
|
||||||
$list->items[] = $item;
|
$list->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Suggest keywords
|
|
||||||
if ($node instanceof Node\Name && $node->getAttribute('parentNode') instanceof Node\Expr\ConstFetch) {
|
if (!isset($creation)) {
|
||||||
foreach (self::KEYWORDS as $keyword) {
|
foreach (self::KEYWORDS as $keyword) {
|
||||||
if (substr($keyword, 0, $prefixLen) === $prefix) {
|
if (strpos($keyword, $prefix) === 0) {
|
||||||
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||||
$item->insertText = $keyword . ' ';
|
$item->insertText = $keyword . ' ';
|
||||||
$list->items[] = $item;
|
$list->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} elseif (TolerantParserHelpers::isConstantFetch($node)) {
|
||||||
$node instanceof Node\Expr\Variable
|
$prefix = (string) ($node->getResolvedName() ?? Tolerant\ResolvedName::buildName($node->nameParts, $node->getFileContents()));
|
||||||
|| ($node && $node->getAttribute('parentNode') instanceof Node\Expr\Variable)
|
foreach (self::KEYWORDS as $keyword) {
|
||||||
) {
|
if (strpos($keyword, $prefix) === 0) {
|
||||||
// Find variables, parameters and use statements in the scope
|
$item = new CompletionItem($keyword, CompletionItemKind::KEYWORD);
|
||||||
// If there was only a $ typed, $node will be instanceof Node\Error
|
$item->insertText = $keyword . ' ';
|
||||||
$namePrefix = $node instanceof Node\Expr\Variable && is_string($node->name) ? $node->name : '';
|
$list->items[] = $item;
|
||||||
foreach ($this->suggestVariablesAtNode($node, $namePrefix) as $var) {
|
}
|
||||||
$item = new CompletionItem;
|
|
||||||
$item->kind = CompletionItemKind::VARIABLE;
|
|
||||||
$item->label = '$' . ($var instanceof Node\Expr\ClosureUse ? $var->var : $var->name);
|
|
||||||
$item->documentation = $this->definitionResolver->getDocumentationFromNode($var);
|
|
||||||
$item->detail = (string)$this->definitionResolver->getTypeFromNode($var);
|
|
||||||
$item->textEdit = new TextEdit(
|
|
||||||
new Range($pos, $pos),
|
|
||||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), $item->label)
|
|
||||||
);
|
|
||||||
$list->items[] = $item;
|
|
||||||
}
|
}
|
||||||
} else if ($node instanceof Node\Stmt\InlineHTML || $pos == new Position(0, 0)) {
|
|
||||||
$item = new CompletionItem('<?php', CompletionItemKind::KEYWORD);
|
|
||||||
$item->textEdit = new TextEdit(
|
|
||||||
new Range($pos, $pos),
|
|
||||||
stripStringOverlap($doc->getRange(new Range(new Position(0, 0), $pos)), '<?php')
|
|
||||||
);
|
|
||||||
$list->items[] = $item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -303,7 +332,7 @@ class CompletionProvider
|
||||||
foreach ($fqns as $fqn) {
|
foreach ($fqns as $fqn) {
|
||||||
$def = $this->index->getDefinition($fqn);
|
$def = $this->index->getDefinition($fqn);
|
||||||
if ($def) {
|
if ($def) {
|
||||||
foreach ($this->expandParentFqns($def->extends) as $parent) {
|
foreach ($this->expandParentFqns($def->extends ?? []) as $parent) {
|
||||||
$expanded[] = $parent;
|
$expanded[] = $parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +349,7 @@ class CompletionProvider
|
||||||
* @param string $namePrefix Prefix to filter
|
* @param string $namePrefix Prefix to filter
|
||||||
* @return array <Node\Expr\Variable|Node\Param|Node\Expr\ClosureUse>
|
* @return array <Node\Expr\Variable|Node\Param|Node\Expr\ClosureUse>
|
||||||
*/
|
*/
|
||||||
private function suggestVariablesAtNode(Node $node, string $namePrefix = ''): array
|
private function suggestVariablesAtNode(Tolerant\Node $node, string $namePrefix = ''): array
|
||||||
{
|
{
|
||||||
$vars = [];
|
$vars = [];
|
||||||
|
|
||||||
|
@ -336,30 +365,33 @@ class CompletionProvider
|
||||||
|
|
||||||
// Walk the AST upwards until a scope boundary is met
|
// Walk the AST upwards until a scope boundary is met
|
||||||
$level = $node;
|
$level = $node;
|
||||||
while ($level && !($level instanceof Node\FunctionLike)) {
|
while ($level && !TolerantParserHelpers::isFunctionLike($level)) {
|
||||||
// Walk siblings before the node
|
// Walk siblings before the node
|
||||||
$sibling = $level;
|
$sibling = $level;
|
||||||
while ($sibling = $sibling->getAttribute('previousSibling')) {
|
while ($sibling = $sibling->getPreviousSibling()) {
|
||||||
// Collect all variables inside the sibling node
|
// Collect all variables inside the sibling node
|
||||||
foreach ($this->findVariableDefinitionsInNode($sibling, $namePrefix) as $var) {
|
foreach ($this->findVariableDefinitionsInNode($sibling, $namePrefix) as $var) {
|
||||||
$vars[$var->name] = $var;
|
$vars[$var->getName()] = $var;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$level = $level->getAttribute('parentNode');
|
$level = $level->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the traversal ended because a function was met,
|
// If the traversal ended because a function was met,
|
||||||
// also add its parameters and closure uses to the result list
|
// also add its parameters and closure uses to the result list
|
||||||
if ($level instanceof Node\FunctionLike) {
|
if ($level && TolerantParserHelpers::isFunctionLike($level) && $level->parameters !== null) {
|
||||||
foreach ($level->params as $param) {
|
foreach ($level->parameters->getValues() as $param) {
|
||||||
if (!isset($vars[$param->name]) && substr($param->name, 0, strlen($namePrefix)) === $namePrefix) {
|
$paramName = $param->getName();
|
||||||
$vars[$param->name] = $param;
|
if (empty($namePrefix) || strpos($paramName, $namePrefix) !== false) {
|
||||||
|
$vars[$paramName] = $param;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($level instanceof Node\Expr\Closure) {
|
|
||||||
foreach ($level->uses as $use) {
|
if ($level instanceof Tolerant\Node\Expression\AnonymousFunctionCreationExpression && $level->anonymousFunctionUseClause !== null) {
|
||||||
if (!isset($vars[$use->var]) && substr($use->var, 0, strlen($namePrefix)) === $namePrefix) {
|
foreach ($level->anonymousFunctionUseClause->useVariableNameList->getValues() as $use) {
|
||||||
$vars[$use->var] = $use;
|
$useName = $use->getName();
|
||||||
|
if (empty($namePrefix) || strpos($useName, $namePrefix) !== false) {
|
||||||
|
$vars[$useName] = $use;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,36 +407,35 @@ class CompletionProvider
|
||||||
* @param string $namePrefix Prefix to filter
|
* @param string $namePrefix Prefix to filter
|
||||||
* @return Node\Expr\Variable[]
|
* @return Node\Expr\Variable[]
|
||||||
*/
|
*/
|
||||||
private function findVariableDefinitionsInNode(Node $node, string $namePrefix = ''): array
|
private function findVariableDefinitionsInNode(Tolerant\Node $node, string $namePrefix = ''): array
|
||||||
{
|
{
|
||||||
$vars = [];
|
$vars = [];
|
||||||
// If the child node is a variable assignment, save it
|
// If the child node is a variable assignment, save it
|
||||||
$parent = $node->getAttribute('parentNode');
|
|
||||||
if (
|
$isAssignmentToVariable = function ($node) use ($namePrefix) {
|
||||||
$node instanceof Node\Expr\Variable
|
return $node instanceof Tolerant\Node\Expression\AssignmentExpression
|
||||||
&& ($parent instanceof Node\Expr\Assign || $parent instanceof Node\Expr\AssignOp)
|
&& $node->leftOperand instanceof Tolerant\Node\Expression\Variable
|
||||||
&& is_string($node->name) // Variable variables are of no use
|
&& (empty($namePrefix) || strpos($node->leftOperand->getName(), $namePrefix) !== false);
|
||||||
&& substr($node->name, 0, strlen($namePrefix)) === $namePrefix
|
};
|
||||||
) {
|
$isNotFunctionLike = function($node) {
|
||||||
$vars[] = $node;
|
return !(
|
||||||
}
|
TolerantParserHelpers::isFunctionLike($node) ||
|
||||||
// Iterate over subnodes
|
$node instanceof Tolerant\Node\Statement\ClassDeclaration ||
|
||||||
foreach ($node->getSubNodeNames() as $attr) {
|
$node instanceof Tolerant\Node\Statement\InterfaceDeclaration ||
|
||||||
if (!isset($node->$attr)) {
|
$node instanceof Tolerant\Node\Statement\TraitDeclaration
|
||||||
continue;
|
);
|
||||||
}
|
};
|
||||||
$children = is_array($node->$attr) ? $node->$attr : [$node->$attr];
|
|
||||||
foreach ($children as $child) {
|
if ($isAssignmentToVariable($node)) {
|
||||||
// Dont try to traverse scalars
|
$vars[] = $node->leftOperand;
|
||||||
// Dont traverse functions, the contained variables are in a different scope
|
} else {
|
||||||
if (!($child instanceof Node) || $child instanceof Node\FunctionLike) {
|
foreach ($node->getDescendantNodes($isNotFunctionLike) as $descendantNode) {
|
||||||
continue;
|
if ($isAssignmentToVariable($descendantNode)) {
|
||||||
}
|
$vars[] = $descendantNode->leftOperand;
|
||||||
foreach ($this->findVariableDefinitionsInNode($child, $namePrefix) as $var) {
|
|
||||||
$vars[] = $var;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $vars;
|
return $vars;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,7 +240,7 @@ class PhpDocument
|
||||||
* Returns the node at a specified position
|
* Returns the node at a specified position
|
||||||
*
|
*
|
||||||
* @param Position $position
|
* @param Position $position
|
||||||
* @return Node|null
|
* @return Tolerant\Node|null
|
||||||
*/
|
*/
|
||||||
public function getNodeAtPosition(Position $position)
|
public function getNodeAtPosition(Position $position)
|
||||||
{
|
{
|
||||||
|
|
|
@ -520,6 +520,7 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
|
||||||
$n = $n->expression;
|
$n = $n->expression;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
// TODO - clean this up
|
||||||
($n instanceof Tolerant\Node\Expression\AssignmentExpression && $n->operator->kind === Tolerant\TokenKind::EqualsToken)
|
($n instanceof Tolerant\Node\Expression\AssignmentExpression && $n->operator->kind === Tolerant\TokenKind::EqualsToken)
|
||||||
&& $n->leftOperand instanceof Tolerant\Node\Expression\Variable && $n->leftOperand->getName() === $name
|
&& $n->leftOperand instanceof Tolerant\Node\Expression\Variable && $n->leftOperand->getName() === $name
|
||||||
) {
|
) {
|
||||||
|
@ -865,6 +866,10 @@ class TolerantDefinitionResolver implements DefinitionResolverInterface
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($expr instanceof Tolerant\Node\QualifiedName) {
|
||||||
|
return $this->resolveClassNameToType($expr);
|
||||||
|
}
|
||||||
|
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue