Add support for getting references of variables
parent
a5f8e86095
commit
ccfbf9c8f2
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node, NodeTraverser};
|
||||
|
||||
/**
|
||||
* Collects all references to a variable
|
||||
*/
|
||||
class VariableReferencesCollector extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* Array of references to the variable
|
||||
*
|
||||
* @var Node\Expr\Variable[]
|
||||
*/
|
||||
public $references = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @param string $name The variable name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue