1
0
Fork 0
php-language-server/src/TolerantTreeAnalyzer.php

186 lines
6.3 KiB
PHP
Raw Normal View History

2017-03-05 02:36:17 +00:00
<?php
declare(strict_types = 1);
namespace LanguageServer;
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit};
use LanguageServer\NodeVisitor\{
NodeAtPositionFinder,
ReferencesAdder,
DocBlockParser,
DefinitionCollector,
ColumnCalculator,
ReferencesCollector
};
use LanguageServer\Index\Index;
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser, Parser};
use PhpParser\NodeVisitor\NameResolver;
use phpDocumentor\Reflection\DocBlockFactory;
use Sabre\Uri;
use Microsoft\PhpParser as Tolerant;
class TolerantTreeAnalyzer implements TreeAnalyzerInterface {
private $parser;
/** @var Tolerant\Node */
2017-03-05 02:36:17 +00:00
private $stmts;
2017-04-10 21:18:19 +00:00
private $diagnostics;
private $content;
2017-03-21 22:25:49 +00:00
/**
* TolerantTreeAnalyzer constructor.
* @param Tolerant\Parser $parser
* @param $content
* @param $docBlockFactory
* @param TolerantDefinitionResolver $definitionResolver
* @param $uri
*/
public function __construct($parser, $content, $docBlockFactory, $definitionResolver, $uri) {
2017-03-05 02:36:17 +00:00
$this->uri = $uri;
$this->parser = $parser;
$this->docBlockFactory = $docBlockFactory;
$this->definitionResolver = $definitionResolver;
$this->content = $content;
$this->stmts = $this->parser->parseSourceFile($content, $uri);
2017-03-05 02:36:17 +00:00
// TODO - docblock errors
2017-04-10 21:18:19 +00:00
$this->collectDefinitionsAndReferences($this->stmts);
}
public function collectDefinitionsAndReferences(Tolerant\Node $stmts) {
foreach ($stmts::CHILD_NAMES as $name) {
$node = $stmts->$name;
if ($node === null) {
continue;
}
if (\is_array($node)) {
foreach ($node as $child) {
if ($child instanceof Tolerant\Node) {
$this->update($child);
}
}
continue;
}
if ($node instanceof Tolerant\Node) {
$this->update($node);
}
if (($_error = Tolerant\DiagnosticsProvider::checkDiagnostics($node)) !== null) {
$range = Tolerant\PositionUtilities::getRangeFromPosition($_error->start, $_error->length, $this->content);
$this->diagnostics[] = new Diagnostic(
$_error->message,
new Range(
new Position($range->start->line, $range->start->character),
new Position($range->end->line, $range->start->character)
)
);
}
}
}
public function update($node) {
$fqn = ($this->definitionResolver)::getDefinedFqn($node);
// Only index definitions with an FQN (no variables)
if ($fqn !== null) {
$this->definitionNodes[$fqn] = $node;
$this->definitions[$fqn] = $this->definitionResolver->createDefinitionFromNode($node, $fqn);
}
$parent = $node->parent;
if (!(
(
// $node->parent instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
($node instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
$node instanceof Tolerant\Node\Expression\MemberAccessExpression)
2017-04-10 21:18:19 +00:00
&& !(
$node->parent instanceof Tolerant\Node\Expression\CallExpression ||
$node->memberName instanceof Tolerant\Token
))
|| ($parent instanceof Tolerant\Node\Statement\NamespaceDefinition && $parent->name !== null && $parent->name->getStart() === $node->getStart()))
) {
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
if ($fqn !== null) {
$this->addReference($fqn, $node);
if (
$node instanceof Tolerant\Node\QualifiedName
&& $node->isQualifiedName()
&& !($parent instanceof Tolerant\Node\Statement\NamespaceDefinition && $parent->name->getStart() === $node->getStart()
)
) {
// Add references for each referenced namespace
$ns = $fqn;
while (($pos = strrpos($ns, '\\')) !== false) {
$ns = substr($ns, 0, $pos);
$this->addReference($ns, $node);
}
2017-03-28 21:02:26 +00:00
}
2017-04-10 21:18:19 +00:00
// 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
if (TolerantDefinitionResolver::isConstantFetch($node) ||
($parent instanceof Tolerant\Node\Expression\CallExpression
&& !(
$parent->callableExpression instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
$node instanceof Tolerant\Node\Expression\ScopedPropertyAccessExpression ||
$node instanceof Tolerant\Node\Expression\MemberAccessExpression
2017-04-10 21:18:19 +00:00
))) {
$parts = explode('\\', $fqn);
if (count($parts) > 1) {
$globalFqn = end($parts);
$this->addReference($globalFqn, $node);
}
}
}
}
$this->collectDefinitionsAndReferences($node);
2017-03-21 22:25:49 +00:00
}
public function getDiagnostics() {
2017-03-28 21:02:26 +00:00
return $this->diagnostics ?? [];
2017-03-05 02:36:17 +00:00
}
2017-03-09 23:56:41 +00:00
private function addReference(string $fqn, Tolerant\Node $node)
{
if (!isset($this->referenceNodes[$fqn])) {
$this->referenceNodes[$fqn] = [];
}
$this->referenceNodes[$fqn][] = $node;
}
2017-03-05 02:36:17 +00:00
public function getDefinitions() {
return $this->definitions ?? [];
}
public function getDefinitionNodes() {
return $this->definitionNodes ?? [];
}
public function getReferenceNodes() {
return $this->referenceNodes ?? [];
}
public function getStmts() {
return $this->stmts;
}
/**
* Returns the URI of the document
*
* @return string
*/
public function getUri(): string
{
return $this->uri;
}
}