Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fixtures/completion/property.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php

$obj = new TestClass;
$obj = new ChildClass;
$obj->
3 changes: 3 additions & 0 deletions fixtures/global_references.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ function whatever(TestClass $param): TestClass {
// Nested expression
$obj->testProperty->testMethod();
TestClass::$staticTestProperty[123]->testProperty;

$child = new ChildClass;
echo $child->testMethod();
2 changes: 2 additions & 0 deletions fixtures/global_symbols.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ public function testMethod($testParameter)
$testVariable = 123;
}
};

class ChildClass extends TestClass {}
3 changes: 3 additions & 0 deletions fixtures/references.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ function whatever(TestClass $param): TestClass {
// Nested expressions
$obj->testProperty->testMethod();
TestClass::$staticTestProperty[123]->testProperty;

$child = new ChildClass;
echo $child->testMethod();
2 changes: 2 additions & 0 deletions fixtures/symbols.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ public function testMethod($testParameter)
$testVariable = 123;
}
};

class ChildClass extends TestClass {}
23 changes: 23 additions & 0 deletions src/CompletionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi
$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) {
Expand All @@ -158,6 +160,7 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi
$prefix .= '::$';
}
}
unset($prefix);

foreach ($this->index->getDefinitions() as $fqn => $def) {
foreach ($prefixes as $prefix) {
Expand Down Expand Up @@ -287,6 +290,26 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi
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
Expand Down
7 changes: 7 additions & 0 deletions src/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 39 additions & 4 deletions src/DefinitionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ public function createDefinitionFromNode(Node $node, string $fqn = null): Defini
|| ($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);
Expand Down Expand Up @@ -248,7 +259,31 @@ public function resolveReferenceNodeToFqn(Node $node)
} 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;
Expand Down Expand Up @@ -290,16 +325,16 @@ public function resolveReferenceNodeToFqn(Node $node)
} else {
return null;
}
if (!isset($name)) {
return null;
}
if (
$node instanceof Node\Expr\MethodCall
|| $node instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\FuncCall
) {
$name .= '()';
}
if (!isset($name)) {
return null;
}
return $name;
}

Expand Down
4 changes: 3 additions & 1 deletion tests/NodeVisitor/DefinitionCollectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public function testCollectsSymbols()
'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']);
Expand All @@ -61,6 +62,7 @@ public function testCollectsSymbols()
$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()
Expand Down
46 changes: 29 additions & 17 deletions tests/Server/ServerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public function setUp()
// 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))),
Expand All @@ -86,6 +87,7 @@ public function setUp()
'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))),
Expand All @@ -104,14 +106,18 @@ public function setUp()
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
Expand All @@ -137,7 +143,8 @@ public function setUp()
],
'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))),
Expand All @@ -150,13 +157,17 @@ public function setUp()
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
Expand All @@ -182,7 +193,8 @@ public function setUp()
],
'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))),
Expand Down
18 changes: 18 additions & 0 deletions tests/Server/TextDocument/CompletionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ public function testNewInNamespace()
null,
'\TestClass'
),
new CompletionItem(
'ChildClass',
CompletionItemKind::CLASS_,
null,
null,
null,
null,
'\ChildClass'
),
// Namespaced, `use`d TestClass definition (inserted as TestClass)
new CompletionItem(
'TestClass',
Expand All @@ -175,6 +184,15 @@ public function testNewInNamespace()
null,
'TestClass'
),
new CompletionItem(
'ChildClass',
CompletionItemKind::CLASS_,
'TestNamespace',
null,
null,
null,
'\TestNamespace\ChildClass'
),
], true), $items);
}

Expand Down
12 changes: 12 additions & 0 deletions tests/Server/TextDocument/Definition/GlobalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ public function testDefinitionForMethods()
$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;
Expand Down
1 change: 1 addition & 0 deletions tests/Server/TextDocument/DocumentSymbolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function test()
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
}
Expand Down
Loading