From ccfbf9c8f288a7b61fcd469559ae40fadcf1d1f2 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 11 Oct 2016 20:38:02 +0200 Subject: [PATCH] Add support for getting references of variables --- .../VariableReferencesCollector.php | 50 ++++++++++++++ src/PhpDocument.php | 66 +++++++++++++++++-- src/Server/TextDocument.php | 21 ++---- .../References/NamespacedTest.php | 28 +++++++- 4 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 src/NodeVisitor/VariableReferencesCollector.php diff --git a/src/NodeVisitor/VariableReferencesCollector.php b/src/NodeVisitor/VariableReferencesCollector.php new file mode 100644 index 0000000..a113a7e --- /dev/null +++ b/src/NodeVisitor/VariableReferencesCollector.php @@ -0,0 +1,50 @@ +name = $name; + } + + public function enterNode(Node $node) + { + if ($node instanceof Node\Expr\Variable && $node->name === $this->name) { + $this->references[] = $node; + } else if ($node instanceof Node\FunctionLike) { + // If we meet a function node, dont traverse its statements, they are in another scope + // except it is a closure that has imported the variable through use + if ($node instanceof Node\Expr\Closure) { + foreach ($node->uses as $use) { + if ($use->var === $this->name) { + return; + } + } + } + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } +} diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 5f7ee32..7279d44 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -9,7 +9,8 @@ use LanguageServer\NodeVisitor\{ ReferencesAdder, DefinitionCollector, ColumnCalculator, - ReferencesCollector + ReferencesCollector, + VariableReferencesCollector }; use PhpParser\{Error, Node, NodeTraverser, Parser}; use PhpParser\NodeVisitor\NameResolver; @@ -58,7 +59,7 @@ class PhpDocument * * @var Node[] */ - private $statements; + private $stmts; /** * Map from fully qualified name (FQN) to Node @@ -177,7 +178,7 @@ class PhpDocument $this->project->addReferenceDocument($fqn, $this); } - $this->statements = $stmts; + $this->stmts = $stmts; } } @@ -214,6 +215,16 @@ class PhpDocument return $this->uri; } + /** + * Returns the AST of the document + * + * @return Node[] + */ + public function getStmts(): array + { + return $this->stmts; + } + /** * Returns the node at a specified position * @@ -225,7 +236,7 @@ class PhpDocument $traverser = new NodeTraverser; $finder = new NodeAtPositionFinder($position); $traverser->addVisitor($finder); - $traverser->traverse($this->statements); + $traverser->traverse($this->stmts); return $finder->node; } @@ -453,6 +464,53 @@ class PhpDocument return $document->getDefinitionByFqn($fqn); } + /** + * Returns the reference nodes for any node + * The references node MAY be in other documents, check the ownerDocument attribute + * + * @param Node $node + * @return Node[] + */ + public function getReferencesByNode(Node $node) + { + // Variables always stay in the boundary of the file and need to be searched inside their function scope + // by traversing the AST + if ($node instanceof Node\Expr\Variable) { + if ($node->name instanceof Node\Expr) { + return null; + } + // Find function/method/closure scope + $n = $node; + while (isset($n) && !($n instanceof Node\FunctionLike)) { + $n = $n->getAttribute('parentNode'); + } + if (!isset($n)) { + $n = $node->getAttribute('ownerDocument'); + } + $traverser = new NodeTraverser; + $refCollector = new VariableReferencesCollector($node->name); + $traverser->addVisitor($refCollector); + $traverser->traverse($n->getStmts()); + return $refCollector->references; + } + // Definition with a global FQN + $fqn = $this->getDefinedFqn($node); + if ($fqn === null) { + return []; + } + $refDocuments = $this->project->getReferenceDocuments($fqn); + $nodes = []; + foreach ($refDocuments as $document) { + $refs = $document->getReferencesByFqn($fqn); + if ($refs !== null) { + foreach ($refs as $ref) { + $nodes[] = $ref; + } + } + } + return $nodes; + } + /** * Returns the assignment or parameter node where a variable was defined * diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index fcee7d7..59fac4e 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -110,28 +110,19 @@ class TextDocument * denoted by the given text document position. * * @param ReferenceContext $context - * @return Location[]|null + * @return Location[] */ - public function references(ReferenceContext $context, TextDocumentIdentifier $textDocument, Position $position) + public function references(ReferenceContext $context, TextDocumentIdentifier $textDocument, Position $position): array { $document = $this->project->getDocument($textDocument->uri); $node = $document->getNodeAtPosition($position); if ($node === null) { - return null; + return []; } - $fqn = $document->getDefinedFqn($node); - if ($fqn === null) { - return null; - } - $refDocuments = $this->project->getReferenceDocuments($fqn); + $refs = $document->getReferencesByNode($node); $locations = []; - foreach ($refDocuments as $document) { - $refs = $document->getReferencesByFqn($fqn); - if ($refs !== null) { - foreach ($refs as $ref) { - $locations[] = Location::fromNode($ref); - } - } + foreach ($refs as $ref) { + $locations[] = Location::fromNode($ref); } return $locations; } diff --git a/tests/Server/TextDocument/References/NamespacedTest.php b/tests/Server/TextDocument/References/NamespacedTest.php index b9b3229..3ff31ae 100644 --- a/tests/Server/TextDocument/References/NamespacedTest.php +++ b/tests/Server/TextDocument/References/NamespacedTest.php @@ -278,6 +278,19 @@ class NamespacedTest extends TestCase // Get definition for $var $result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($this->referencesUri), new Position(13, 7)); $this->assertEquals([ + [ + 'uri' => $this->referencesUri, + 'range' => [ + 'start' => [ + 'line' => 12, + 'character' => 0 + ], + 'end' => [ + 'line' => 12, + 'character' => 4 + ] + ] + ], [ 'uri' => $this->referencesUri, 'range' => [ @@ -287,7 +300,20 @@ class NamespacedTest extends TestCase ], 'end' => [ 'line' => 13, - 'character' => 8 + 'character' => 9 + ] + ] + ], + [ + 'uri' => $this->referencesUri, + 'range' => [ + 'start' => [ + 'line' => 20, + 'character' => 9 + ], + 'end' => [ + 'line' => 20, + 'character' => 13 ] ] ]