Add support for inherited members (#218)
in completion, definition, references, hover etcpull/219/head v4.1.0
parent
cc9d5e987b
commit
a4a13e6528
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
|
||||
$obj = new TestClass;
|
||||
$obj = new ChildClass;
|
||||
$obj->
|
||||
|
|
|
@ -38,3 +38,6 @@ if ($abc instanceof TestInterface) {
|
|||
// Nested expression
|
||||
$obj->testProperty->testMethod();
|
||||
TestClass::$staticTestProperty[123]->testProperty;
|
||||
|
||||
$child = new ChildClass;
|
||||
echo $child->testMethod();
|
||||
|
|
|
@ -96,3 +96,5 @@ new class {
|
|||
$testVariable = 123;
|
||||
}
|
||||
};
|
||||
|
||||
class ChildClass extends TestClass {}
|
||||
|
|
|
@ -38,3 +38,6 @@ if ($abc instanceof TestInterface) {
|
|||
// Nested expressions
|
||||
$obj->testProperty->testMethod();
|
||||
TestClass::$staticTestProperty[123]->testProperty;
|
||||
|
||||
$child = new ChildClass;
|
||||
echo $child->testMethod();
|
||||
|
|
|
@ -96,3 +96,5 @@ new class {
|
|||
$testVariable = 123;
|
||||
}
|
||||
};
|
||||
|
||||
class ChildClass extends TestClass {}
|
||||
|
|
|
@ -145,8 +145,10 @@ class CompletionProvider
|
|||
$this->definitionResolver->resolveExpressionNodeToType($node->var)
|
||||
);
|
||||
} else {
|
||||
// Static member reference
|
||||
$prefixes = [$node->class instanceof Node\Name ? (string)$node->class : ''];
|
||||
}
|
||||
$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) {
|
||||
|
@ -158,6 +160,7 @@ class CompletionProvider
|
|||
$prefix .= '::$';
|
||||
}
|
||||
}
|
||||
unset($prefix);
|
||||
|
||||
foreach ($this->index->getDefinitions() as $fqn => $def) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
|
@ -287,6 +290,26 @@ class CompletionProvider
|
|||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the FQNs of all parent classes to an array of FQNs of classes
|
||||
*
|
||||
* @param string[] $fqns
|
||||
* @return string[]
|
||||
*/
|
||||
private function expandParentFqns(array $fqns): array
|
||||
{
|
||||
$expanded = $fqns;
|
||||
foreach ($fqns as $fqn) {
|
||||
$def = $this->index->getDefinition($fqn);
|
||||
if ($def) {
|
||||
foreach ($this->expandParentFqns($def->extends) as $parent) {
|
||||
$expanded[] = $parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will walk the AST upwards until a function-like node is met
|
||||
* and at each level walk all previous siblings and their children to search for definitions
|
||||
|
|
|
@ -30,6 +30,13 @@ class Definition
|
|||
*/
|
||||
public $fqn;
|
||||
|
||||
/**
|
||||
* For class or interfaces, the FQNs of extended classes and implemented interfaces
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $extends;
|
||||
|
||||
/**
|
||||
* Only true for classes, interfaces, traits, functions and non-class constants
|
||||
* This is so methods and properties are not suggested in the global scope
|
||||
|
|
|
@ -115,6 +115,17 @@ class DefinitionResolver
|
|||
|| ($node instanceof Node\Stmt\PropertyProperty && $node->getAttribute('parentNode')->isStatic())
|
||||
);
|
||||
$def->fqn = $fqn;
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$def->extends = [];
|
||||
if ($node->extends) {
|
||||
$def->extends[] = (string)$node->extends;
|
||||
}
|
||||
} else if ($node instanceof Node\Stmt\Interface_) {
|
||||
$def->extends = [];
|
||||
foreach ($node->extends as $n) {
|
||||
$def->extends[] = (string)$n;
|
||||
}
|
||||
}
|
||||
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
||||
$def->type = $this->getTypeFromNode($node);
|
||||
$def->declarationLine = $this->getDeclarationLineFromNode($node);
|
||||
|
@ -248,7 +259,31 @@ class DefinitionResolver
|
|||
} else {
|
||||
$classFqn = substr((string)$varType->getFqsen(), 1);
|
||||
}
|
||||
$name = $classFqn . '->' . (string)$node->name;
|
||||
$memberSuffix = '->' . (string)$node->name;
|
||||
if ($node instanceof Node\Expr\MethodCall) {
|
||||
$memberSuffix .= '()';
|
||||
}
|
||||
// Find the right class that implements the member
|
||||
$implementorFqns = [$classFqn];
|
||||
while ($implementorFqn = array_shift($implementorFqns)) {
|
||||
// If the member FQN exists, return it
|
||||
if ($this->index->getDefinition($implementorFqn . $memberSuffix)) {
|
||||
return $implementorFqn . $memberSuffix;
|
||||
}
|
||||
// Get Definition of implementor class
|
||||
$implementorDef = $this->index->getDefinition($implementorFqn);
|
||||
// If it doesn't exist, return the initial guess
|
||||
if ($implementorDef === null) {
|
||||
break;
|
||||
}
|
||||
// Repeat for parent class
|
||||
if ($implementorDef->extends) {
|
||||
foreach ($implementorDef->extends as $extends) {
|
||||
$implementorFqns[] = $extends;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $classFqn . $memberSuffix;
|
||||
} else if ($parent instanceof Node\Expr\FuncCall) {
|
||||
if ($parent->name instanceof Node\Expr) {
|
||||
return null;
|
||||
|
@ -290,6 +325,9 @@ class DefinitionResolver
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
if (!isset($name)) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
$node instanceof Node\Expr\MethodCall
|
||||
|| $node instanceof Node\Expr\StaticCall
|
||||
|
@ -297,9 +335,6 @@ class DefinitionResolver
|
|||
) {
|
||||
$name .= '()';
|
||||
}
|
||||
if (!isset($name)) {
|
||||
return null;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,8 @@ class DefinitionCollectorTest extends TestCase
|
|||
'TestNamespace\\TestClass->testMethod()',
|
||||
'TestNamespace\\TestTrait',
|
||||
'TestNamespace\\TestInterface',
|
||||
'TestNamespace\\test_function()'
|
||||
'TestNamespace\\test_function()',
|
||||
'TestNamespace\\ChildClass'
|
||||
], array_keys($defNodes));
|
||||
$this->assertInstanceOf(Node\Const_::class, $defNodes['TestNamespace\\TEST_CONST']);
|
||||
$this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\TestClass']);
|
||||
|
@ -61,6 +62,7 @@ class DefinitionCollectorTest extends TestCase
|
|||
$this->assertInstanceOf(Node\Stmt\Trait_::class, $defNodes['TestNamespace\\TestTrait']);
|
||||
$this->assertInstanceOf(Node\Stmt\Interface_::class, $defNodes['TestNamespace\\TestInterface']);
|
||||
$this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\test_function()']);
|
||||
$this->assertInstanceOf(Node\Stmt\Class_::class, $defNodes['TestNamespace\\ChildClass']);
|
||||
}
|
||||
|
||||
public function testDoesNotCollectReferences()
|
||||
|
|
|
@ -71,6 +71,7 @@ abstract class ServerTestCase extends TestCase
|
|||
// Global
|
||||
'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
||||
'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
||||
'ChildClass' => new Location($globalSymbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
||||
'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
||||
'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||
'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
||||
|
@ -86,6 +87,7 @@ abstract class ServerTestCase extends TestCase
|
|||
'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))),
|
||||
'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
||||
'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
||||
'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
||||
'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
||||
'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||
'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
||||
|
@ -104,14 +106,18 @@ abstract class ServerTestCase extends TestCase
|
|||
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
||||
],
|
||||
'TestNamespace\\TestClass' => [
|
||||
0 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
||||
1 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
||||
2 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
||||
3 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||
4 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||
5 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||
6 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||
7 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass;
|
||||
0 => new Location($symbolsUri , new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
||||
1 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
||||
2 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
||||
3 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
||||
4 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||
5 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||
6 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||
7 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||
8 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass;
|
||||
],
|
||||
'TestNamespace\\TestChild' => [
|
||||
0 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
||||
],
|
||||
'TestNamespace\\TestInterface' => [
|
||||
0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||
|
@ -137,7 +143,8 @@ abstract class ServerTestCase extends TestCase
|
|||
],
|
||||
'TestNamespace\\TestClass::testMethod()' => [
|
||||
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod();
|
||||
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 32))) // $obj->testProperty->testMethod();
|
||||
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod();
|
||||
],
|
||||
'TestNamespace\\test_function()' => [
|
||||
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||
|
@ -150,13 +157,17 @@ abstract class ServerTestCase extends TestCase
|
|||
1 => new Location($globalReferencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
||||
],
|
||||
'TestClass' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
||||
1 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
||||
2 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
||||
3 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||
4 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||
5 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||
6 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||
0 => new Location($globalSymbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
||||
1 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
||||
2 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
||||
3 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
||||
4 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||
5 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||
6 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||
7 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||
],
|
||||
'TestChild' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
||||
],
|
||||
'TestInterface' => [
|
||||
0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||
|
@ -182,7 +193,8 @@ abstract class ServerTestCase extends TestCase
|
|||
],
|
||||
'TestClass::testMethod()' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 18))), // $obj->testMethod();
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 32))) // $obj->testProperty->testMethod();
|
||||
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 32))), // $obj->testProperty->testMethod();
|
||||
2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))) // $child->testMethod();
|
||||
],
|
||||
'test_function()' => [
|
||||
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||
|
|
|
@ -165,6 +165,15 @@ class CompletionTest extends TestCase
|
|||
null,
|
||||
'\TestClass'
|
||||
),
|
||||
new CompletionItem(
|
||||
'ChildClass',
|
||||
CompletionItemKind::CLASS_,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'\ChildClass'
|
||||
),
|
||||
// Namespaced, `use`d TestClass definition (inserted as TestClass)
|
||||
new CompletionItem(
|
||||
'TestClass',
|
||||
|
@ -175,6 +184,15 @@ class CompletionTest extends TestCase
|
|||
null,
|
||||
'TestClass'
|
||||
),
|
||||
new CompletionItem(
|
||||
'ChildClass',
|
||||
CompletionItemKind::CLASS_,
|
||||
'TestNamespace',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'\TestNamespace\ChildClass'
|
||||
),
|
||||
], true), $items);
|
||||
}
|
||||
|
||||
|
|
|
@ -161,6 +161,18 @@ class GlobalTest extends ServerTestCase
|
|||
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
||||
}
|
||||
|
||||
public function testDefinitionForMethodOnChildClass()
|
||||
{
|
||||
// $child->testMethod();
|
||||
// Get definition for testMethod
|
||||
$reference = $this->getReferenceLocations('TestClass::testMethod()')[2];
|
||||
$result = $this->textDocument->definition(
|
||||
new TextDocumentIdentifier($reference->uri),
|
||||
$reference->range->end
|
||||
)->wait();
|
||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
||||
}
|
||||
|
||||
public function testDefinitionForProperties()
|
||||
{
|
||||
// echo $obj->testProperty;
|
||||
|
|
|
@ -29,6 +29,7 @@ class DocumentSymbolTest extends ServerTestCase
|
|||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||
], $result);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class SymbolTest extends ServerTestCase
|
|||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
||||
// Global
|
||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
||||
|
@ -53,6 +54,7 @@ class SymbolTest extends ServerTestCase
|
|||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''),
|
||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''),
|
||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''),
|
||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''),
|
||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''),
|
||||
|
||||
new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), '')
|
||||
|
|
Loading…
Reference in New Issue