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,
|
ReferencesAdder,
|
||||||
DefinitionCollector,
|
DefinitionCollector,
|
||||||
ColumnCalculator,
|
ColumnCalculator,
|
||||||
ReferencesCollector
|
ReferencesCollector,
|
||||||
|
VariableReferencesCollector
|
||||||
};
|
};
|
||||||
use PhpParser\{Error, Node, NodeTraverser, Parser};
|
use PhpParser\{Error, Node, NodeTraverser, Parser};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
|
@ -58,7 +59,7 @@ class PhpDocument
|
||||||
*
|
*
|
||||||
* @var Node[]
|
* @var Node[]
|
||||||
*/
|
*/
|
||||||
private $statements;
|
private $stmts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map from fully qualified name (FQN) to Node
|
* Map from fully qualified name (FQN) to Node
|
||||||
|
@ -177,7 +178,7 @@ class PhpDocument
|
||||||
$this->project->addReferenceDocument($fqn, $this);
|
$this->project->addReferenceDocument($fqn, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->statements = $stmts;
|
$this->stmts = $stmts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,6 +215,16 @@ class PhpDocument
|
||||||
return $this->uri;
|
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
|
* Returns the node at a specified position
|
||||||
*
|
*
|
||||||
|
@ -225,7 +236,7 @@ class PhpDocument
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$finder = new NodeAtPositionFinder($position);
|
$finder = new NodeAtPositionFinder($position);
|
||||||
$traverser->addVisitor($finder);
|
$traverser->addVisitor($finder);
|
||||||
$traverser->traverse($this->statements);
|
$traverser->traverse($this->stmts);
|
||||||
return $finder->node;
|
return $finder->node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,6 +464,53 @@ class PhpDocument
|
||||||
return $document->getDefinitionByFqn($fqn);
|
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
|
* Returns the assignment or parameter node where a variable was defined
|
||||||
*
|
*
|
||||||
|
|
|
@ -110,29 +110,20 @@ class TextDocument
|
||||||
* denoted by the given text document position.
|
* denoted by the given text document position.
|
||||||
*
|
*
|
||||||
* @param ReferenceContext $context
|
* @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);
|
$document = $this->project->getDocument($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
$fqn = $document->getDefinedFqn($node);
|
$refs = $document->getReferencesByNode($node);
|
||||||
if ($fqn === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$refDocuments = $this->project->getReferenceDocuments($fqn);
|
|
||||||
$locations = [];
|
$locations = [];
|
||||||
foreach ($refDocuments as $document) {
|
|
||||||
$refs = $document->getReferencesByFqn($fqn);
|
|
||||||
if ($refs !== null) {
|
|
||||||
foreach ($refs as $ref) {
|
foreach ($refs as $ref) {
|
||||||
$locations[] = Location::fromNode($ref);
|
$locations[] = Location::fromNode($ref);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return $locations;
|
return $locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,19 @@ class NamespacedTest extends TestCase
|
||||||
// Get definition for $var
|
// Get definition for $var
|
||||||
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($this->referencesUri), new Position(13, 7));
|
$result = $this->textDocument->references(new ReferenceContext, new TextDocumentIdentifier($this->referencesUri), new Position(13, 7));
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
|
[
|
||||||
|
'uri' => $this->referencesUri,
|
||||||
|
'range' => [
|
||||||
|
'start' => [
|
||||||
|
'line' => 12,
|
||||||
|
'character' => 0
|
||||||
|
],
|
||||||
|
'end' => [
|
||||||
|
'line' => 12,
|
||||||
|
'character' => 4
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'uri' => $this->referencesUri,
|
'uri' => $this->referencesUri,
|
||||||
'range' => [
|
'range' => [
|
||||||
|
@ -287,7 +300,20 @@ class NamespacedTest extends TestCase
|
||||||
],
|
],
|
||||||
'end' => [
|
'end' => [
|
||||||
'line' => 13,
|
'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