diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 4eb8833..d5849a0 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -102,17 +102,18 @@ class DefinitionResolver */ public function createDefinitionFromNode(Node $node, string $fqn = null): Definition { + $parent = $node->getAttribute('parentNode'); $def = new Definition; $def->canBeInstantiated = $node instanceof Node\Stmt\Class_; $def->isGlobal = ( $node instanceof Node\Stmt\ClassLike - || $node instanceof Node\Stmt\Namespace_ + || ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) || $node instanceof Node\Stmt\Function_ - || $node->getAttribute('parentNode') instanceof Node\Stmt\Const_ + || $parent instanceof Node\Stmt\Const_ ); $def->isStatic = ( ($node instanceof Node\Stmt\ClassMethod && $node->isStatic()) - || ($node instanceof Node\Stmt\PropertyProperty && $node->getAttribute('parentNode')->isStatic()) + || ($node instanceof Node\Stmt\PropertyProperty && $parent->isStatic()) ); $def->fqn = $fqn; if ($node instanceof Node\Stmt\Class_) { @@ -203,9 +204,9 @@ class DefinitionResolver if ( $node instanceof Node\Name && ( $parent instanceof Node\Stmt\ClassLike - || $parent instanceof Node\Stmt\Namespace_ || $parent instanceof Node\Param || $parent instanceof Node\FunctionLike + || $parent instanceof Node\Stmt\GroupUse || $parent instanceof Node\Expr\New_ || $parent instanceof Node\Expr\StaticCall || $parent instanceof Node\Expr\ClassConstFetch @@ -804,12 +805,13 @@ class DefinitionResolver */ public static function getDefinedFqn(Node $node) { + $parent = $node->getAttribute('parentNode'); // Anonymous classes don't count as a definition if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) { // Class, interface or trait declaration return (string)$node->namespacedName; - } else if ($node instanceof Node\Stmt\Namespace_) { - return (string)$node->name; + } else if ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) { + return (string)$node; } else if ($node instanceof Node\Stmt\Function_) { // Function: use functionName() as the name return (string)$node->namespacedName . '()'; diff --git a/src/NodeVisitor/ReferencesCollector.php b/src/NodeVisitor/ReferencesCollector.php index ae3027e..e7b9f06 100644 --- a/src/NodeVisitor/ReferencesCollector.php +++ b/src/NodeVisitor/ReferencesCollector.php @@ -37,11 +37,24 @@ class ReferencesCollector extends NodeVisitorAbstract // Check if the node references any global symbol $fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node); if ($fqn) { + $parent = $node->getAttribute('parentNode'); + $grandParent = $parent ? $parent->getAttribute('parentNode') : null; $this->addReference($fqn, $node); + if ( + $node instanceof Node\Name + && $node->isQualified() + && !($parent instanceof Node\Stmt\Namespace_ && $parent->name === $node) + ) { + // Add references for each referenced namespace + $ns = $fqn; + while (($pos = strrpos($ns, '\\')) !== false) { + $ns = substr($ns, 0, $pos); + $this->addReference($ns, $node); + } + } // Namespaced constant access and function calls also need to register a reference // to the global version because PHP falls back to global at runtime // http://php.net/manual/en/language.namespaces.fallback.php - $parent = $node->getAttribute('parentNode'); if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) { $parts = explode('\\', $fqn); if (count($parts) > 1) { diff --git a/src/Protocol/SymbolInformation.php b/src/Protocol/SymbolInformation.php index 06e8f7e..5d442f0 100644 --- a/src/Protocol/SymbolInformation.php +++ b/src/Protocol/SymbolInformation.php @@ -48,6 +48,7 @@ class SymbolInformation */ public static function fromNode(Node $node, string $fqn = null) { + $parent = $node->getAttribute('parentNode'); $symbol = new self; if ($node instanceof Node\Stmt\Class_) { $symbol->kind = SymbolKind::CLASS_; @@ -55,7 +56,7 @@ class SymbolInformation $symbol->kind = SymbolKind::CLASS_; } else if ($node instanceof Node\Stmt\Interface_) { $symbol->kind = SymbolKind::INTERFACE; - } else if ($node instanceof Node\Stmt\Namespace_) { + } else if ($node instanceof Node\Name && $parent instanceof Node\Stmt\Namespace_) { $symbol->kind = SymbolKind::NAMESPACE; } else if ($node instanceof Node\Stmt\Function_) { $symbol->kind = SymbolKind::FUNCTION; @@ -77,7 +78,9 @@ class SymbolInformation } else { return null; } - if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp) { + if ($node instanceof Node\Name) { + $symbol->name = (string)$node; + } else if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp) { $symbol->name = $node->var->name; } else if ($node instanceof Node\Expr\ClosureUse) { $symbol->name = $node->var; diff --git a/tests/NodeVisitor/DefinitionCollectorTest.php b/tests/NodeVisitor/DefinitionCollectorTest.php index f4e053e..3ee9995 100644 --- a/tests/NodeVisitor/DefinitionCollectorTest.php +++ b/tests/NodeVisitor/DefinitionCollectorTest.php @@ -87,7 +87,8 @@ class DefinitionCollectorTest extends TestCase $defNodes = $definitionCollector->nodes; $this->assertEquals(['TestNamespace', 'TestNamespace\\whatever()'], array_keys($defNodes)); - $this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']); + $this->assertInstanceOf(Node\Name::class, $defNodes['TestNamespace']); + $this->assertInstanceOf(Node\Stmt\Namespace_::class, $defNodes['TestNamespace']->getAttribute('parentNode')); $this->assertInstanceOf(Node\Stmt\Function_::class, $defNodes['TestNamespace\\whatever()']); } } diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index d46c80e..a4661a2 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -83,8 +83,8 @@ abstract class ServerTestCase extends TestCase 'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))), // Namespaced - 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 0), new Position( 2, 24))), - 'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))), + 'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 10), new Position( 2, 23))), + 'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 10), new Position( 2, 29))), '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))), @@ -102,6 +102,11 @@ abstract class ServerTestCase extends TestCase $this->referenceLocations = [ // Namespaced + 'TestNamespace' => [ + 0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function; + 1 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass; + 2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 17))) // use TestNamespace\{TestTrait, TestInterface}; + ], 'TestNamespace\\TEST_CONST' => [ 0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))) ], diff --git a/tests/Server/TextDocument/References/NamespacedTest.php b/tests/Server/TextDocument/References/NamespacedTest.php index 930a9c8..1ee1ab4 100644 --- a/tests/Server/TextDocument/References/NamespacedTest.php +++ b/tests/Server/TextDocument/References/NamespacedTest.php @@ -3,7 +3,7 @@ declare(strict_types = 1); namespace LanguageServer\Tests\Server\TextDocument\References; -use LanguageServer\Protocol\Location; +use LanguageServer\Protocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range}; use function LanguageServer\pathToUri; class NamespacedTest extends GlobalTest @@ -17,4 +17,17 @@ class NamespacedTest extends GlobalTest { return parent::getDefinitionLocation('TestNamespace\\' . $fqn); } + + public function testReferencesForNamespaces() + { + // namespace TestNamespace; + // Get references for TestNamespace + $definition = parent::getDefinitionLocation('TestNamespace'); + $result = $this->textDocument->references( + new ReferenceContext, + new TextDocumentIdentifier($definition->uri), + $definition->range->end + )->wait(); + $this->assertEquals(parent::getReferenceLocations('TestNamespace'), $result); + } } diff --git a/tests/Server/Workspace/SymbolTest.php b/tests/Server/Workspace/SymbolTest.php index ea80e1b..b88735c 100644 --- a/tests/Server/Workspace/SymbolTest.php +++ b/tests/Server/Workspace/SymbolTest.php @@ -29,7 +29,7 @@ class SymbolTest extends ServerTestCase $referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php')); // @codingStandardsIgnoreStart $this->assertEquals([ - new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''), + new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 10), new Position(2, 23))), ''), // Namespaced new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'), new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),