Fluent interfaces support (#421)
parent
94fc0405fd
commit
35f33c8c91
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
class Grand
|
||||||
|
{
|
||||||
|
/** @return $this */
|
||||||
|
public function foo()
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Parent1 extends Grand
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent1
|
||||||
|
{
|
||||||
|
public function bar()
|
||||||
|
{
|
||||||
|
$this->foo()->q
|
||||||
|
}
|
||||||
|
public function qux()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use LanguageServer\Protocol\{
|
||||||
};
|
};
|
||||||
use Microsoft\PhpParser;
|
use Microsoft\PhpParser;
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
class CompletionProvider
|
class CompletionProvider
|
||||||
{
|
{
|
||||||
|
@ -196,18 +197,15 @@ class CompletionProvider
|
||||||
// $a->|
|
// $a->|
|
||||||
|
|
||||||
// Multiple prefixes for all possible types
|
// Multiple prefixes for all possible types
|
||||||
$prefixes = FqnUtilities\getFqnsFromType(
|
$fqns = FqnUtilities\getFqnsFromType(
|
||||||
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
$this->definitionResolver->resolveExpressionNodeToType($node->dereferencableExpression)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Include parent classes
|
// Add the object access operator to only get members of all parents
|
||||||
$prefixes = $this->expandParentFqns($prefixes);
|
$prefixes = [];
|
||||||
|
foreach ($this->expandParentFqns($fqns) as $prefix) {
|
||||||
// Add the object access operator to only get members
|
$prefixes[] = $prefix . '->';
|
||||||
foreach ($prefixes as &$prefix) {
|
|
||||||
$prefix .= '->';
|
|
||||||
}
|
}
|
||||||
unset($prefix);
|
|
||||||
|
|
||||||
// Collect all definitions that match any of the prefixes
|
// Collect all definitions that match any of the prefixes
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
|
@ -232,18 +230,15 @@ class CompletionProvider
|
||||||
// TODO: $a::|
|
// TODO: $a::|
|
||||||
|
|
||||||
// Resolve all possible types to FQNs
|
// Resolve all possible types to FQNs
|
||||||
$prefixes = FqnUtilities\getFqnsFromType(
|
$fqns = FqnUtilities\getFqnsFromType(
|
||||||
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
$classType = $this->definitionResolver->resolveExpressionNodeToType($scoped->scopeResolutionQualifier)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add parent classes
|
// Append :: operator to only get static members of all parents
|
||||||
$prefixes = $this->expandParentFqns($prefixes);
|
$prefixes = [];
|
||||||
|
foreach ($this->expandParentFqns($fqns) as $prefix) {
|
||||||
// Append :: operator to only get static members
|
$prefixes[] = $prefix . '::';
|
||||||
foreach ($prefixes as &$prefix) {
|
|
||||||
$prefix .= '::';
|
|
||||||
}
|
}
|
||||||
unset($prefix);
|
|
||||||
|
|
||||||
// Collect all definitions that match any of the prefixes
|
// Collect all definitions that match any of the prefixes
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||||
|
@ -377,23 +372,22 @@ class CompletionProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the FQNs of all parent classes to an array of FQNs of classes
|
* Yields FQNs from an array along with the FQNs of all parent classes
|
||||||
*
|
*
|
||||||
* @param string[] $fqns
|
* @param string[] $fqns
|
||||||
* @return string[]
|
* @return Generator
|
||||||
*/
|
*/
|
||||||
private function expandParentFqns(array $fqns): array
|
private function expandParentFqns(array $fqns) : Generator
|
||||||
{
|
{
|
||||||
$expanded = $fqns;
|
|
||||||
foreach ($fqns as $fqn) {
|
foreach ($fqns as $fqn) {
|
||||||
|
yield $fqn;
|
||||||
$def = $this->index->getDefinition($fqn);
|
$def = $this->index->getDefinition($fqn);
|
||||||
if ($def) {
|
if ($def !== null) {
|
||||||
foreach ($this->expandParentFqns($def->extends ?? []) as $parent) {
|
foreach ($def->getAncestorDefinitions($this->index) as $name => $def) {
|
||||||
$expanded[] = $parent;
|
yield $name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $expanded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,9 +3,11 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
use phpDocumentor\Reflection\{Types, Type, Fqsen, TypeResolver};
|
||||||
use LanguageServer\Protocol\SymbolInformation;
|
use LanguageServer\Protocol\SymbolInformation;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to represent symbols
|
* Class used to represent symbols
|
||||||
|
@ -95,4 +97,33 @@ class Definition
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $documentation;
|
public $documentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields the definitons of all ancestor classes (the Definition fqn is yielded as key)
|
||||||
|
*
|
||||||
|
* @param ReadableIndex $index the index to search for needed definitions
|
||||||
|
* @param bool $includeSelf should the first yielded value be the current definition itself
|
||||||
|
* @return Generator
|
||||||
|
*/
|
||||||
|
public function getAncestorDefinitions(ReadableIndex $index, bool $includeSelf = false): Generator
|
||||||
|
{
|
||||||
|
if ($includeSelf) {
|
||||||
|
yield $this->fqn => $this;
|
||||||
|
}
|
||||||
|
if ($this->extends !== null) {
|
||||||
|
// iterating once, storing the references and iterating again
|
||||||
|
// guarantees that closest definitions are yielded first
|
||||||
|
$definitions = [];
|
||||||
|
foreach ($this->extends as $fqn) {
|
||||||
|
$def = $index->getDefinition($fqn);
|
||||||
|
if ($def !== null) {
|
||||||
|
yield $def->fqn => $def;
|
||||||
|
$definitions[] = $def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($definitions as $def) {
|
||||||
|
yield from $def->getAncestorDefinitions($index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -574,7 +574,6 @@ class DefinitionResolver
|
||||||
// $this -> Type\this
|
// $this -> Type\this
|
||||||
// $myVariable -> type of corresponding assignment expression
|
// $myVariable -> type of corresponding assignment expression
|
||||||
if ($expr instanceof Node\Expression\Variable || $expr instanceof Node\UseVariableName) {
|
if ($expr instanceof Node\Expression\Variable || $expr instanceof Node\UseVariableName) {
|
||||||
// TODO: this will need to change when fluent interfaces are supported
|
|
||||||
if ($expr->getName() === 'this') {
|
if ($expr->getName() === 'this') {
|
||||||
return new Types\Object_(new Fqsen('\\' . $this->getContainingClassFqn($expr)));
|
return new Types\Object_(new Fqsen('\\' . $this->getContainingClassFqn($expr)));
|
||||||
}
|
}
|
||||||
|
@ -667,13 +666,21 @@ class DefinitionResolver
|
||||||
} else {
|
} else {
|
||||||
$classFqn = substr((string)$t->getFqsen(), 1);
|
$classFqn = substr((string)$t->getFqsen(), 1);
|
||||||
}
|
}
|
||||||
$fqn = $classFqn . '->' . $expr->memberName->getText($expr->getFileContents());
|
$add = '->' . $expr->memberName->getText($expr->getFileContents());
|
||||||
if ($expr->parent instanceof Node\Expression\CallExpression) {
|
if ($expr->parent instanceof Node\Expression\CallExpression) {
|
||||||
$fqn .= '()';
|
$add .= '()';
|
||||||
}
|
}
|
||||||
$def = $this->index->getDefinition($fqn);
|
$classDef = $this->index->getDefinition($classFqn);
|
||||||
if ($def !== null) {
|
if ($classDef !== null) {
|
||||||
return $def->type;
|
foreach ($classDef->getAncestorDefinitions($this->index, true) as $fqn => $def) {
|
||||||
|
$def = $this->index->getDefinition($fqn . $add);
|
||||||
|
if ($def !== null) {
|
||||||
|
if ($def->type instanceof Types\This) {
|
||||||
|
return new Types\Object_(new Fqsen('\\' . $classFqn));
|
||||||
|
}
|
||||||
|
return $def->type;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,4 +653,31 @@ class CompletionTest extends TestCase
|
||||||
)
|
)
|
||||||
], true), $items);
|
], true), $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testThisReturnValue()
|
||||||
|
{
|
||||||
|
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/this_return_value.php');
|
||||||
|
$this->loader->open($completionUri, file_get_contents($completionUri));
|
||||||
|
$items = $this->textDocument->completion(
|
||||||
|
new TextDocumentIdentifier($completionUri),
|
||||||
|
new Position(17, 23)
|
||||||
|
)->wait();
|
||||||
|
$this->assertEquals(new CompletionList([
|
||||||
|
new CompletionItem(
|
||||||
|
'foo',
|
||||||
|
CompletionItemKind::METHOD,
|
||||||
|
'$this' // Return type of the method
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'bar',
|
||||||
|
CompletionItemKind::METHOD,
|
||||||
|
'mixed' // Return type of the method
|
||||||
|
),
|
||||||
|
new CompletionItem(
|
||||||
|
'qux',
|
||||||
|
CompletionItemKind::METHOD,
|
||||||
|
'mixed' // Return type of the method
|
||||||
|
)
|
||||||
|
], true), $items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue