2016-09-30 09:30:08 +00:00
|
|
|
<?php
|
2016-09-30 09:54:49 +00:00
|
|
|
declare(strict_types = 1);
|
2016-09-30 09:30:08 +00:00
|
|
|
|
|
|
|
namespace LanguageServer;
|
|
|
|
|
2016-10-11 13:28:53 +00:00
|
|
|
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit};
|
2016-10-09 14:03:56 +00:00
|
|
|
use LanguageServer\NodeVisitor\{NodeAtPositionFinder, ReferencesAdder, DefinitionCollector, ColumnCalculator};
|
2016-10-11 13:28:53 +00:00
|
|
|
use PhpParser\{Error, Node, NodeTraverser, Parser};
|
2016-09-30 09:30:08 +00:00
|
|
|
use PhpParser\NodeVisitor\NameResolver;
|
|
|
|
|
|
|
|
class PhpDocument
|
|
|
|
{
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* The LanguageClient instance (to report errors etc)
|
|
|
|
*
|
|
|
|
* @var LanguageClient
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $client;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Project this document belongs to (to register definitions etc)
|
|
|
|
*
|
|
|
|
* @var Project
|
|
|
|
*/
|
2016-10-08 12:59:08 +00:00
|
|
|
public $project;
|
|
|
|
// for whatever reason I get "cannot access private property" error if $project is not public
|
|
|
|
// https://github.com/felixfbecker/php-language-server/pull/49#issuecomment-252427359
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The PHPParser instance
|
|
|
|
*
|
|
|
|
* @var Parser
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $parser;
|
|
|
|
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* The URI of the document
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $uri;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The content of the document
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $content;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
/**
|
|
|
|
* The AST of the document
|
|
|
|
*
|
|
|
|
* @var Node[]
|
|
|
|
*/
|
2016-10-11 12:42:56 +00:00
|
|
|
private $statements;
|
2016-10-08 12:59:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Map from fully qualified name (FQN) to Node
|
|
|
|
*
|
|
|
|
* @var Node[]
|
|
|
|
*/
|
2016-10-11 12:42:56 +00:00
|
|
|
private $definitions;
|
2016-10-08 12:59:08 +00:00
|
|
|
|
2016-10-09 08:09:09 +00:00
|
|
|
/**
|
|
|
|
* Map from fully qualified name (FQN) to array of nodes that reference the symbol
|
|
|
|
*
|
|
|
|
* @var Node[][]
|
|
|
|
*/
|
|
|
|
private $references;
|
|
|
|
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* @param string $uri The URI of the document
|
2016-10-11 12:42:56 +00:00
|
|
|
* @param string $content The content of the document
|
2016-10-08 10:51:55 +00:00
|
|
|
* @param Project $project The Project this document belongs to (to register definitions etc)
|
|
|
|
* @param LanguageClient $client The LanguageClient instance (to report errors etc)
|
|
|
|
* @param Parser $parser The PHPParser instance
|
|
|
|
*/
|
2016-10-11 12:42:56 +00:00
|
|
|
public function __construct(string $uri, string $content, Project $project, LanguageClient $client, Parser $parser)
|
2016-09-30 09:30:08 +00:00
|
|
|
{
|
|
|
|
$this->uri = $uri;
|
|
|
|
$this->project = $project;
|
|
|
|
$this->client = $client;
|
|
|
|
$this->parser = $parser;
|
2016-10-11 12:42:56 +00:00
|
|
|
$this->updateContent($content);
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-11 12:42:56 +00:00
|
|
|
* Re-parses a source file, updates symbols and reports parsing errors
|
|
|
|
* that may have occured as diagnostics.
|
2016-09-30 09:30:08 +00:00
|
|
|
*
|
|
|
|
* @param string $content
|
2016-10-08 10:51:55 +00:00
|
|
|
* @return void
|
2016-09-30 09:30:08 +00:00
|
|
|
*/
|
|
|
|
public function updateContent(string $content)
|
|
|
|
{
|
|
|
|
$this->content = $content;
|
|
|
|
$stmts = null;
|
|
|
|
$errors = [];
|
|
|
|
try {
|
2016-10-11 12:42:56 +00:00
|
|
|
$stmts = $this->parser->parse($content);
|
2016-10-08 10:51:55 +00:00
|
|
|
} catch (\PhpParser\Error $e) {
|
2016-09-30 09:30:08 +00:00
|
|
|
// Lexer can throw errors. e.g for unterminated comments
|
|
|
|
// unfortunately we don't get a location back
|
|
|
|
$errors[] = $e;
|
|
|
|
}
|
|
|
|
|
|
|
|
$errors = array_merge($this->parser->getErrors(), $errors);
|
|
|
|
|
|
|
|
$diagnostics = [];
|
|
|
|
foreach ($errors as $error) {
|
|
|
|
$diagnostic = new Diagnostic();
|
2016-10-11 08:15:20 +00:00
|
|
|
$startLine = max($error->getStartLine() - 1, 0);
|
|
|
|
$startColumn = $error->hasColumnInfo() ? $error->getStartColumn($this->content) - 1 : 0;
|
|
|
|
$endLine = max($error->getEndLine() - 1, $startLine);
|
|
|
|
$endColumn = $error->hasColumnInfo() ? $error->getEndColumn($this->content) : 0;
|
|
|
|
$diagnostic->range = new Range(new Position($startLine, $startColumn), new Position($endLine, $endColumn));
|
2016-09-30 09:30:08 +00:00
|
|
|
$diagnostic->severity = DiagnosticSeverity::ERROR;
|
|
|
|
$diagnostic->source = 'php';
|
|
|
|
// Do not include "on line ..." in the error message
|
|
|
|
$diagnostic->message = $error->getRawMessage();
|
|
|
|
$diagnostics[] = $diagnostic;
|
|
|
|
}
|
|
|
|
$this->client->textDocument->publishDiagnostics($this->uri, $diagnostics);
|
|
|
|
|
|
|
|
// $stmts can be null in case of a fatal parsing error
|
|
|
|
if ($stmts) {
|
|
|
|
$traverser = new NodeTraverser;
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Resolve aliased names to FQNs
|
2016-09-30 09:30:08 +00:00
|
|
|
$traverser->addVisitor(new NameResolver);
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Add parentNode, previousSibling, nextSibling attributes
|
2016-10-08 12:53:13 +00:00
|
|
|
$traverser->addVisitor(new ReferencesAdder($this));
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Add column attributes to nodes
|
2016-10-11 12:42:56 +00:00
|
|
|
$traverser->addVisitor(new ColumnCalculator($content));
|
2016-10-08 11:22:34 +00:00
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
// Collect all definitions
|
|
|
|
$definitionCollector = new DefinitionCollector;
|
|
|
|
$traverser->addVisitor($definitionCollector);
|
|
|
|
|
2016-09-30 09:30:08 +00:00
|
|
|
$traverser->traverse($stmts);
|
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
// Register this document on the project for all the symbols defined in it
|
|
|
|
foreach ($definitionCollector->definitions as $fqn => $node) {
|
2016-10-11 12:42:56 +00:00
|
|
|
$this->project->setDefinitionUri($fqn, $this->uri);
|
2016-10-08 12:59:08 +00:00
|
|
|
}
|
|
|
|
|
2016-10-11 12:42:56 +00:00
|
|
|
$this->statements = $stmts;
|
|
|
|
$this->definitions = $definitionCollector->definitions;
|
2016-10-08 11:34:49 +00:00
|
|
|
}
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-10 13:06:02 +00:00
|
|
|
* Returns array of TextEdit changes to format this document.
|
2016-09-30 09:30:08 +00:00
|
|
|
*
|
2016-10-10 13:06:02 +00:00
|
|
|
* @return \LanguageServer\Protocol\TextEdit[]
|
2016-09-30 09:30:08 +00:00
|
|
|
*/
|
|
|
|
public function getFormattedText()
|
|
|
|
{
|
2016-10-11 12:42:56 +00:00
|
|
|
if (empty($this->content)) {
|
2016-09-30 09:30:08 +00:00
|
|
|
return [];
|
|
|
|
}
|
2016-10-10 13:06:02 +00:00
|
|
|
return Formatter::format($this->content, $this->uri);
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this document's text content.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getContent()
|
|
|
|
{
|
|
|
|
return $this->content;
|
|
|
|
}
|
2016-10-08 11:34:49 +00:00
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
/**
|
|
|
|
* Returns the URI of the document
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getUri(): string
|
|
|
|
{
|
|
|
|
return $this->uri;
|
|
|
|
}
|
|
|
|
|
2016-10-08 11:34:49 +00:00
|
|
|
/**
|
|
|
|
* Returns the node at a specified position
|
|
|
|
*
|
|
|
|
* @param Position $position
|
|
|
|
* @return Node|null
|
|
|
|
*/
|
|
|
|
public function getNodeAtPosition(Position $position)
|
|
|
|
{
|
|
|
|
$traverser = new NodeTraverser;
|
|
|
|
$finder = new NodeAtPositionFinder($position);
|
|
|
|
$traverser->addVisitor($finder);
|
2016-10-11 12:42:56 +00:00
|
|
|
$traverser->traverse($this->statements);
|
2016-10-08 11:34:49 +00:00
|
|
|
return $finder->node;
|
|
|
|
}
|
2016-10-08 12:59:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the definition node for a fully qualified name
|
|
|
|
*
|
|
|
|
* @param string $fqn
|
|
|
|
* @return Node|null
|
|
|
|
*/
|
|
|
|
public function getDefinitionByFqn(string $fqn)
|
|
|
|
{
|
|
|
|
return $this->definitions[$fqn] ?? null;
|
|
|
|
}
|
|
|
|
|
2016-10-11 12:42:56 +00:00
|
|
|
/**
|
|
|
|
* Returns a map from fully qualified name (FQN) to Nodes defined in this document
|
|
|
|
*
|
|
|
|
* @return Node[]
|
|
|
|
*/
|
|
|
|
public function getDefinitions()
|
|
|
|
{
|
|
|
|
return $this->definitions;
|
|
|
|
}
|
|
|
|
|
2016-10-08 12:59:08 +00:00
|
|
|
/**
|
2016-10-09 08:09:09 +00:00
|
|
|
* Returns true if the given FQN is defined in this document
|
|
|
|
*
|
|
|
|
* @param string $fqn The fully qualified name of the symbol
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isDefined(string $fqn): bool
|
|
|
|
{
|
|
|
|
return isset($this->definitions[$fqn]);
|
|
|
|
}
|
|
|
|
|
2016-10-09 12:40:15 +00:00
|
|
|
/**
|
|
|
|
* Returns the fully qualified name (FQN) that is defined by a node
|
|
|
|
* Examples of FQNs:
|
|
|
|
* - testFunction()
|
|
|
|
* - TestNamespace\TestClass
|
|
|
|
* - TestNamespace\TestClass::TEST_CONSTANT
|
|
|
|
* - TestNamespace\TestClass::staticTestProperty
|
|
|
|
* - TestNamespace\TestClass::testProperty
|
|
|
|
* - TestNamespace\TestClass::staticTestMethod()
|
|
|
|
* - TestNamespace\TestClass::testMethod()
|
|
|
|
*
|
|
|
|
* @param Node $node
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getDefinedFqn(Node $node)
|
|
|
|
{
|
|
|
|
// Anonymous classes don't count as a definition
|
2016-10-11 12:42:56 +00:00
|
|
|
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
|
2016-10-09 12:40:15 +00:00
|
|
|
// Class, interface or trait declaration
|
|
|
|
return (string)$node->namespacedName;
|
2016-10-11 12:42:56 +00:00
|
|
|
} else if ($node instanceof Node\Stmt\Function_) {
|
2016-10-09 12:40:15 +00:00
|
|
|
// Function: use functionName() as the name
|
|
|
|
return (string)$node->namespacedName . '()';
|
|
|
|
} else if ($node instanceof Node\Stmt\ClassMethod) {
|
|
|
|
// Class method: use ClassName::methodName() as name
|
|
|
|
$class = $node->getAttribute('parentNode');
|
|
|
|
if (!isset($class->name)) {
|
|
|
|
// Ignore anonymous classes
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (string)$class->namespacedName . '::' . (string)$node->name . '()';
|
|
|
|
} else if ($node instanceof Node\Stmt\PropertyProperty) {
|
|
|
|
// Property: use ClassName::propertyName as name
|
|
|
|
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
|
|
|
|
if (!isset($class->name)) {
|
|
|
|
// Ignore anonymous classes
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (string)$class->namespacedName . '::' . (string)$node->name;
|
|
|
|
} else if ($node instanceof Node\Const_) {
|
|
|
|
$parent = $node->getAttribute('parentNode');
|
|
|
|
if ($parent instanceof Node\Stmt\Const_) {
|
|
|
|
// Basic constant: use CONSTANT_NAME as name
|
|
|
|
return (string)$node->namespacedName;
|
|
|
|
}
|
|
|
|
if ($parent instanceof Node\Stmt\ClassConst) {
|
|
|
|
// Class constant: use ClassName::CONSTANT_NAME as name
|
|
|
|
$class = $parent->getAttribute('parentNode');
|
|
|
|
if (!isset($class->name) || $class->name instanceof Node\Expr) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (string)$class->namespacedName . '::' . $node->name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-09 08:09:09 +00:00
|
|
|
/**
|
|
|
|
* Returns the FQN that is referenced by a node
|
2016-10-08 12:59:08 +00:00
|
|
|
*
|
|
|
|
* @param Node $node
|
2016-10-09 08:09:09 +00:00
|
|
|
* @return string|null
|
2016-10-08 12:59:08 +00:00
|
|
|
*/
|
2016-10-09 08:09:09 +00:00
|
|
|
public function getReferencedFqn(Node $node)
|
2016-10-08 12:59:08 +00:00
|
|
|
{
|
|
|
|
if ($node instanceof Node\Name) {
|
|
|
|
$nameNode = $node;
|
|
|
|
$node = $node->getAttribute('parentNode');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
($node instanceof Node\Stmt\ClassLike
|
|
|
|
|| $node instanceof Node\Param
|
|
|
|
|| $node instanceof Node\Stmt\Function_)
|
|
|
|
&& isset($nameNode)
|
|
|
|
) {
|
|
|
|
// For extends, implements and type hints use the name directly
|
|
|
|
$name = (string)$nameNode;
|
2016-10-09 08:09:09 +00:00
|
|
|
// Only the name node should be considered a reference, not the UseUse node itself
|
|
|
|
} else if ($node instanceof Node\Stmt\UseUse && isset($nameNode)) {
|
2016-10-08 12:59:08 +00:00
|
|
|
$name = (string)$node->name;
|
|
|
|
$parent = $node->getAttribute('parentNode');
|
|
|
|
if ($parent instanceof Node\Stmt\GroupUse) {
|
|
|
|
$name = $parent->prefix . '\\' . $name;
|
|
|
|
}
|
2016-10-09 08:09:09 +00:00
|
|
|
// Only the name node should be considered a reference, not the New_ node itself
|
|
|
|
} else if ($node instanceof Node\Expr\New_ && isset($nameNode)) {
|
2016-10-08 12:59:08 +00:00
|
|
|
if (!($node->class instanceof Node\Name)) {
|
|
|
|
// Cannot get definition of dynamic calls
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name = (string)$node->class;
|
|
|
|
} else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
|
|
|
|
if ($node->name instanceof Node\Expr || !($node->var instanceof Node\Expr\Variable)) {
|
|
|
|
// Cannot get definition of dynamic calls
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Need to resolve variable to a class
|
|
|
|
$varDef = $this->getVariableDefinition($node->var);
|
|
|
|
if (!isset($varDef)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if ($varDef instanceof Node\Param) {
|
|
|
|
if (!isset($varDef->type)) {
|
|
|
|
// Cannot resolve to class without a type hint
|
|
|
|
// TODO: parse docblock
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name = (string)$varDef->type;
|
|
|
|
} else if ($varDef instanceof Node\Expr\Assign) {
|
|
|
|
if ($varDef->expr instanceof Node\Expr\New_) {
|
|
|
|
if (!($varDef->expr->class instanceof Node\Name)) {
|
|
|
|
// Cannot get definition of dynamic calls
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name = (string)$varDef->expr->class;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name .= '::' . (string)$node->name;
|
|
|
|
} else if ($node instanceof Node\Expr\FuncCall) {
|
|
|
|
if ($node->name instanceof Node\Expr) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name = (string)$node->name;
|
|
|
|
} else if ($node instanceof Node\Expr\ConstFetch) {
|
|
|
|
$name = (string)$node->name;
|
|
|
|
} else if (
|
|
|
|
$node instanceof Node\Expr\ClassConstFetch
|
|
|
|
|| $node instanceof Node\Expr\StaticPropertyFetch
|
|
|
|
|| $node instanceof Node\Expr\StaticCall
|
|
|
|
) {
|
|
|
|
if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) {
|
|
|
|
// Cannot get definition of dynamic names
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$name = (string)$node->class . '::' . $node->name;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
$node instanceof Node\Expr\MethodCall
|
|
|
|
|| $node instanceof Node\Expr\FuncCall
|
|
|
|
|| $node instanceof Node\Expr\StaticCall
|
|
|
|
) {
|
|
|
|
$name .= '()';
|
|
|
|
}
|
|
|
|
if (!isset($name)) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-10-09 08:09:09 +00:00
|
|
|
// If the node is a function or constant, it could be namespaced, but PHP falls back to global
|
|
|
|
// The NameResolver therefor does not resolve these to namespaced names
|
|
|
|
// http://php.net/manual/en/language.namespaces.fallback.php
|
|
|
|
if ($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\ConstFetch) {
|
2016-10-08 12:59:08 +00:00
|
|
|
// Find and try with namespace
|
|
|
|
$n = $node;
|
|
|
|
while (isset($n)) {
|
|
|
|
$n = $n->getAttribute('parentNode');
|
|
|
|
if ($n instanceof Node\Stmt\Namespace_) {
|
2016-10-09 08:09:09 +00:00
|
|
|
$namespacedName = (string)$n->name . '\\' . $name;
|
|
|
|
// If the namespaced version is defined, return that
|
|
|
|
// Otherwise fall back to global
|
|
|
|
if ($this->project->isDefined($namespacedName)) {
|
|
|
|
return $namespacedName;
|
|
|
|
}
|
2016-10-08 12:59:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-09 08:09:09 +00:00
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the definition node for any node
|
|
|
|
* The definition node MAY be in another document, check the ownerDocument attribute
|
|
|
|
*
|
|
|
|
* @param Node $node
|
|
|
|
* @return Node|null
|
|
|
|
*/
|
|
|
|
public function getDefinitionByNode(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) {
|
|
|
|
return $this->getVariableDefinition($node);
|
|
|
|
}
|
|
|
|
$fqn = $this->getReferencedFqn($node);
|
|
|
|
if (!isset($fqn)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$document = $this->project->getDefinitionDocument($fqn);
|
2016-10-08 12:59:08 +00:00
|
|
|
if (!isset($document)) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-10-09 08:09:09 +00:00
|
|
|
return $document->getDefinitionByFqn($fqn);
|
2016-10-08 12:59:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the assignment or parameter node where a variable was defined
|
|
|
|
*
|
|
|
|
* @param Node\Expr\Variable $n The variable access
|
|
|
|
* @return Node\Expr\Assign|Node\Param|Node\Expr\ClosureUse|null
|
|
|
|
*/
|
|
|
|
public function getVariableDefinition(Node\Expr\Variable $var)
|
|
|
|
{
|
|
|
|
$n = $var;
|
|
|
|
// Traverse the AST up
|
|
|
|
while (isset($n) && $n = $n->getAttribute('parentNode')) {
|
|
|
|
// If a function is met, check the parameters and use statements
|
|
|
|
if ($n instanceof Node\FunctionLike) {
|
|
|
|
foreach ($n->getParams() as $param) {
|
|
|
|
if ($param->name === $var->name) {
|
|
|
|
return $param;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If it is a closure, also check use statements
|
|
|
|
if ($n instanceof Node\Expr\Closure) {
|
|
|
|
foreach ($n->uses as $use) {
|
|
|
|
if ($use->var === $var->name) {
|
|
|
|
return $use;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Check each previous sibling node for a variable assignment to that variable
|
|
|
|
while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) {
|
2016-10-09 17:06:20 +00:00
|
|
|
if ($n instanceof Node\Expr\Assign && $n->var instanceof Node\Expr\Variable && $n->var->name === $var->name) {
|
2016-10-08 12:59:08 +00:00
|
|
|
return $n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return null if nothing was found
|
|
|
|
return null;
|
|
|
|
}
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|