extracted a document factory
parent
429114ff97
commit
e3f2479512
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer;
|
||||
|
@ -6,58 +7,14 @@ namespace LanguageServer;
|
|||
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, TextEdit};
|
||||
use LanguageServer\NodeVisitor\{
|
||||
NodeAtPositionFinder,
|
||||
ReferencesAdder,
|
||||
DocBlockParser,
|
||||
DefinitionCollector,
|
||||
ColumnCalculator,
|
||||
ReferencesCollector,
|
||||
VariableReferencesCollector
|
||||
};
|
||||
use PhpParser\{Error, ErrorHandler, Node, NodeTraverser};
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
|
||||
use Sabre\Uri;
|
||||
|
||||
class PhpDocument
|
||||
{
|
||||
/**
|
||||
* The LanguageClient instance (to report errors etc)
|
||||
*
|
||||
* @var LanguageClient
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* The Project this document belongs to (to register definitions etc)
|
||||
*
|
||||
* @var Project
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* The PHPParser instance
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* The DocBlockFactory instance to parse docblocks
|
||||
*
|
||||
* @var DocBlockFactory
|
||||
*/
|
||||
private $docBlockFactory;
|
||||
|
||||
/**
|
||||
* The DefinitionResolver instance to resolve reference nodes to definitions
|
||||
*
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
|
||||
/**
|
||||
* The URI of the document
|
||||
|
@ -103,28 +60,10 @@ class PhpDocument
|
|||
|
||||
/**
|
||||
* @param string $uri The URI of the document
|
||||
* @param string $content The content of the document
|
||||
* @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
|
||||
* @param DocBlockFactory $docBlockFactory The DocBlockFactory instance to parse docblocks
|
||||
*/
|
||||
public function __construct(
|
||||
string $uri,
|
||||
string $content,
|
||||
Project $project,
|
||||
LanguageClient $client,
|
||||
Parser $parser,
|
||||
DocBlockFactory $docBlockFactory,
|
||||
DefinitionResolver $definitionResolver
|
||||
) {
|
||||
public function __construct(string $uri)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->project = $project;
|
||||
$this->client = $client;
|
||||
$this->parser = $parser;
|
||||
$this->docBlockFactory = $docBlockFactory;
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->updateContent($content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,92 +79,15 @@ class PhpDocument
|
|||
|
||||
/**
|
||||
* Updates the content on this document.
|
||||
* Re-parses a source file, updates symbols and reports parsing errors
|
||||
* that may have occured as diagnostics.
|
||||
*
|
||||
* @param string $content
|
||||
* @param array $stmts
|
||||
* @return void
|
||||
*/
|
||||
public function updateContent(string $content)
|
||||
public function updateContent(string $content, array $stmts)
|
||||
{
|
||||
$this->content = $content;
|
||||
$stmts = null;
|
||||
|
||||
$errorHandler = new ErrorHandler\Collecting;
|
||||
$stmts = $this->parser->parse($content, $errorHandler);
|
||||
|
||||
$diagnostics = [];
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
||||
}
|
||||
|
||||
// $stmts can be null in case of a fatal parsing error
|
||||
if ($stmts) {
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Resolve aliased names to FQNs
|
||||
$traverser->addVisitor(new NameResolver($errorHandler));
|
||||
|
||||
// Add parentNode, previousSibling, nextSibling attributes
|
||||
$traverser->addVisitor(new ReferencesAdder($this));
|
||||
|
||||
// Add column attributes to nodes
|
||||
$traverser->addVisitor(new ColumnCalculator($content));
|
||||
|
||||
// Parse docblocks and add docBlock attributes to nodes
|
||||
$docBlockParser = new DocBlockParser($this->docBlockFactory);
|
||||
$traverser->addVisitor($docBlockParser);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Report errors from parsing docblocks
|
||||
foreach ($docBlockParser->errors as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php');
|
||||
}
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Collect all definitions
|
||||
$definitionCollector = new DefinitionCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
|
||||
// Collect all references
|
||||
$referencesCollector = new ReferencesCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($referencesCollector);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Unregister old definitions
|
||||
if (isset($this->definitions)) {
|
||||
foreach ($this->definitions as $fqn => $definition) {
|
||||
$this->project->removeDefinition($fqn);
|
||||
}
|
||||
}
|
||||
// Register this document on the project for all the symbols defined in it
|
||||
$this->definitions = $definitionCollector->definitions;
|
||||
$this->definitionNodes = $definitionCollector->nodes;
|
||||
foreach ($definitionCollector->definitions as $fqn => $definition) {
|
||||
$this->project->setDefinition($fqn, $definition);
|
||||
}
|
||||
|
||||
// Unregister old references
|
||||
if (isset($this->referenceNodes)) {
|
||||
foreach ($this->referenceNodes as $fqn => $node) {
|
||||
$this->project->removeReferenceUri($fqn, $this->uri);
|
||||
}
|
||||
}
|
||||
// Register this document on the project for references
|
||||
$this->referenceNodes = $referencesCollector->nodes;
|
||||
foreach ($referencesCollector->nodes as $fqn => $nodes) {
|
||||
$this->project->addReferenceUri($fqn, $this->uri);
|
||||
}
|
||||
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
if (!$this->isVendored()) {
|
||||
$this->client->textDocument->publishDiagnostics($this->uri, $diagnostics);
|
||||
}
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,56 +201,28 @@ class PhpDocument
|
|||
return isset($this->definitions[$fqn]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference nodes for any node
|
||||
* The references node MAY be in other documents, check the ownerDocument attribute
|
||||
*
|
||||
* @param Node $node
|
||||
* @return Promise <Node[]>
|
||||
*/
|
||||
public function getReferenceNodesByNode(Node $node): Promise
|
||||
public function setDefinitions(array $definitions)
|
||||
{
|
||||
return coroutine(function () use ($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
|
||||
|| $node instanceof Node\Param
|
||||
|| $node instanceof Node\Expr\ClosureUse
|
||||
) {
|
||||
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->nodes;
|
||||
}
|
||||
// Definition with a global FQN
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
if ($fqn === null) {
|
||||
return [];
|
||||
}
|
||||
$refDocuments = yield $this->project->getReferenceDocuments($fqn);
|
||||
$nodes = [];
|
||||
foreach ($refDocuments as $document) {
|
||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||
if ($refs !== null) {
|
||||
foreach ($refs as $ref) {
|
||||
$nodes[] = $ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $nodes;
|
||||
});
|
||||
$this->definitions = $definitions;
|
||||
}
|
||||
|
||||
public function setDefinitionNodes(array $nodes)
|
||||
{
|
||||
$this->definitionNodes = $nodes;
|
||||
}
|
||||
|
||||
public function setReferenceNodes(array $nodes)
|
||||
{
|
||||
$this->referenceNodes = $nodes;
|
||||
}
|
||||
|
||||
public function hasReferenceNodes() : bool
|
||||
{
|
||||
return isset($this->referenceNodes);
|
||||
}
|
||||
|
||||
public function getReferenceNodes()
|
||||
{
|
||||
return $this->referenceNodes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\NodeVisitor\ColumnCalculator;
|
||||
use LanguageServer\NodeVisitor\DefinitionCollector;
|
||||
use LanguageServer\NodeVisitor\DocBlockParser;
|
||||
use LanguageServer\NodeVisitor\ReferencesAdder;
|
||||
use LanguageServer\NodeVisitor\ReferencesCollector;
|
||||
use LanguageServer\Protocol\ClientCapabilities;
|
||||
use LanguageServer\Protocol\Diagnostic;
|
||||
use LanguageServer\Protocol\DiagnosticSeverity;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use PhpParser\ErrorHandler\Collecting;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
|
||||
class PhpDocumentFactory
|
||||
{
|
||||
/**
|
||||
* Holds account of all definitions, references etc.
|
||||
* @var Project
|
||||
*/
|
||||
private $project;
|
||||
|
||||
/**
|
||||
* Instance of the PHP parser
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* The DocBlockFactory instance to parse docblocks
|
||||
*
|
||||
* @var DocBlockFactory
|
||||
*/
|
||||
private $docBlockFactory;
|
||||
|
||||
/**
|
||||
* The DefinitionResolver instance to resolve reference nodes to Definitions
|
||||
*
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
|
||||
/**
|
||||
* Reference to the language server client interface
|
||||
*
|
||||
* @var LanguageClient
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* The client's capabilities
|
||||
*
|
||||
* @var ClientCapabilities
|
||||
*/
|
||||
private $clientCapabilities;
|
||||
|
||||
public function __construct(LanguageClient $client, Project $project, ClientCapabilities $clientCapabilities = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->clientCapabilities = $clientCapabilities ? : new ClientCapabilities();
|
||||
$this->parser = new Parser;
|
||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||
$this->definitionResolver = new DefinitionResolver($project);
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the document by doing a textDocument/xcontent request to the client.
|
||||
* If the client does not support textDocument/xcontent, tries to read the file from the file system.
|
||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function loadDocument(string $uri): Promise
|
||||
{
|
||||
return coroutine(function () use ($uri) {
|
||||
$limit = 150000;
|
||||
if ($this->clientCapabilities->xcontentProvider) {
|
||||
$content = (yield $this->client->textDocument->xcontent(new Protocol\TextDocumentIdentifier($uri)))->text;
|
||||
$size = strlen($content);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
} else {
|
||||
$path = uriToPath($uri);
|
||||
$size = filesize($path);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
$content = file_get_contents($path);
|
||||
}
|
||||
|
||||
return $this->createDocument($uri, $content, false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a document is loaded and added to the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $content
|
||||
* @param boolean $index
|
||||
* @return void
|
||||
*/
|
||||
public function createDocument(string $uri, string $content, bool $index = true)
|
||||
{
|
||||
if ($this->project->getDocument($uri)) {
|
||||
$document = $this->project->getDocument($uri);
|
||||
} else {
|
||||
$document = new PhpDocument($uri, $this->project);
|
||||
if ($index)
|
||||
$this->project->addDocument($document);
|
||||
}
|
||||
|
||||
$this->handleContent($document, $content);
|
||||
return $document;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the content on this document.
|
||||
* Re-parses a source file, updates symbols and reports parsing errors
|
||||
* that may have occured as diagnostics.
|
||||
*
|
||||
* @param PhpDocument $document
|
||||
* @param string $content
|
||||
* @return void
|
||||
*/
|
||||
private function handleContent(PhpDocument $document, string $content)
|
||||
{
|
||||
$errorHandler = new Collecting;
|
||||
$stmts = $this->parser->parse($content, $errorHandler);
|
||||
|
||||
$diagnostics = [];
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $content, DiagnosticSeverity::ERROR, 'php');
|
||||
}
|
||||
|
||||
// $stmts can be null in case of a fatal parsing error
|
||||
if ($stmts) {
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Resolve aliased names to FQNs
|
||||
$traverser->addVisitor(new NameResolver($errorHandler));
|
||||
|
||||
// Add parentNode, previousSibling, nextSibling attributes
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
|
||||
// Add column attributes to nodes
|
||||
$traverser->addVisitor(new ColumnCalculator($content));
|
||||
|
||||
// Parse docblocks and add docBlock attributes to nodes
|
||||
$docBlockParser = new DocBlockParser($this->docBlockFactory);
|
||||
$traverser->addVisitor($docBlockParser);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Report errors from parsing docblocks
|
||||
foreach ($docBlockParser->errors as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $content, DiagnosticSeverity::WARNING, 'php');
|
||||
}
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
||||
// Collect all definitions
|
||||
$definitionCollector = new DefinitionCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
|
||||
// Collect all references
|
||||
$referencesCollector = new ReferencesCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($referencesCollector);
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
|
||||
// Unregister old definitions
|
||||
if ($document->getDefinitions() !== null) {
|
||||
foreach ($document->getDefinitions() as $fqn => $definition) {
|
||||
$this->project->removeDefinition($fqn);
|
||||
}
|
||||
}
|
||||
// Register this document on the project for all the symbols defined in it
|
||||
$document->setDefinitions($definitionCollector->definitions);
|
||||
$document->setDefinitionNodes($definitionCollector->nodes);
|
||||
foreach ($definitionCollector->definitions as $fqn => $definition) {
|
||||
$this->project->setDefinition($fqn, $definition);
|
||||
}
|
||||
|
||||
// Unregister old references
|
||||
if ($document->hasReferenceNodes()) {
|
||||
foreach ($document->getReferenceNodes() as $fqn => $node) {
|
||||
$this->project->removeReferenceUri($fqn, $document->getUri());
|
||||
}
|
||||
}
|
||||
// Register this document on the project for references
|
||||
$document->setReferenceNodes($referencesCollector->nodes);
|
||||
foreach ($referencesCollector->nodes as $fqn => $nodes) {
|
||||
$this->project->addReferenceUri($fqn, $document->getUri());
|
||||
}
|
||||
}
|
||||
|
||||
$document->updateContent($content, $stmts);
|
||||
|
||||
if (!$document->isVendored()) {
|
||||
$this->client->textDocument->publishDiagnostics($document->getUri(), $diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
228
src/Project.php
228
src/Project.php
|
@ -3,14 +3,20 @@ declare(strict_types = 1);
|
|||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\Protocol\{SymbolInformation, TextDocumentIdentifier, ClientCapabilities};
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use LanguageServer\NodeVisitor\VariableReferencesCollector;
|
||||
use LanguageServer\Protocol\ClientCapabilities;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\NodeTraverser;
|
||||
use Sabre\Event\Promise;
|
||||
use function Sabre\Event\coroutine;
|
||||
|
||||
class Project
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* An associative array [string => PhpDocument]
|
||||
* that maps URIs to loaded PhpDocuments
|
||||
*
|
||||
|
@ -33,49 +39,15 @@ class Project
|
|||
private $references = [];
|
||||
|
||||
/**
|
||||
* Instance of the PHP parser
|
||||
*
|
||||
* @var Parser
|
||||
* @var PhpDocumentFactory
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* The DocBlockFactory instance to parse docblocks
|
||||
*
|
||||
* @var DocBlockFactory
|
||||
*/
|
||||
private $docBlockFactory;
|
||||
|
||||
/**
|
||||
* The DefinitionResolver instance to resolve reference nodes to Definitions
|
||||
*
|
||||
* @var DefinitionResolver
|
||||
*/
|
||||
private $definitionResolver;
|
||||
|
||||
/**
|
||||
* Reference to the language server client interface
|
||||
*
|
||||
* @var LanguageClient
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* The client's capabilities
|
||||
*
|
||||
* @var ClientCapabilities
|
||||
*/
|
||||
private $clientCapabilities;
|
||||
|
||||
private $documentFactory;
|
||||
|
||||
public function __construct(LanguageClient $client, ClientCapabilities $clientCapabilities)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->clientCapabilities = $clientCapabilities;
|
||||
$this->parser = new Parser;
|
||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||
$this->definitionResolver = new DefinitionResolver($this);
|
||||
$this->documentFactory = new PhpDocumentFactory($client, $this, $clientCapabilities);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the document indicated by uri.
|
||||
* Returns null if the document if not loaded.
|
||||
|
@ -89,86 +61,14 @@ class Project
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the document indicated by uri.
|
||||
* If the document is not open, loads it.
|
||||
* Ensures a document is added to the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function getOrLoadDocument(string $uri)
|
||||
{
|
||||
return isset($this->documents[$uri]) ? Promise\resolve($this->documents[$uri]) : $this->loadDocument($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the document by doing a textDocument/xcontent request to the client.
|
||||
* If the client does not support textDocument/xcontent, tries to read the file from the file system.
|
||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function loadDocument(string $uri): Promise
|
||||
{
|
||||
return coroutine(function () use ($uri) {
|
||||
$limit = 150000;
|
||||
if ($this->clientCapabilities->xcontentProvider) {
|
||||
$content = (yield $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri)))->text;
|
||||
$size = strlen($content);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
} else {
|
||||
$path = uriToPath($uri);
|
||||
$size = filesize($path);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
$content = file_get_contents($path);
|
||||
}
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument(
|
||||
$uri,
|
||||
$content,
|
||||
$this,
|
||||
$this->client,
|
||||
$this->parser,
|
||||
$this->docBlockFactory,
|
||||
$this->definitionResolver
|
||||
);
|
||||
}
|
||||
return $document;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a document is loaded and added to the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $content
|
||||
* @param PhpDocument $document
|
||||
* @return void
|
||||
*/
|
||||
public function openDocument(string $uri, string $content)
|
||||
public function addDocument(PhpDocument $document)
|
||||
{
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument(
|
||||
$uri,
|
||||
$content,
|
||||
$this,
|
||||
$this->client,
|
||||
$this->parser,
|
||||
$this->docBlockFactory,
|
||||
$this->definitionResolver
|
||||
);
|
||||
$this->documents[$uri] = $document;
|
||||
}
|
||||
return $document;
|
||||
$this->documents[$document->getUri()] = $document;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -354,4 +254,96 @@ class Project
|
|||
{
|
||||
return isset($this->definitions[$fqn]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the document indicated by uri.
|
||||
* If the document is not open, loads it.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function getOrLoadDocument(string $uri)
|
||||
{
|
||||
$document = $this->getDocument($uri);
|
||||
return isset($document) ? Promise\resolve($document) : $this->loadDocument($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the document by doing a textDocument/xcontent request to the client.
|
||||
* If the client does not support textDocument/xcontent, tries to read the file from the file system.
|
||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function loadDocument(string $uri): Promise
|
||||
{
|
||||
return $this->documentFactory->loadDocument($uri);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a document is loaded and added to the list of open documents.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $content
|
||||
* @return void
|
||||
*/
|
||||
public function openDocument(string $uri, string $content)
|
||||
{
|
||||
return $this->documentFactory->createDocument($uri, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference nodes for any node
|
||||
* The references node MAY be in other documents, check the ownerDocument attribute
|
||||
*
|
||||
* @param Node2 $node
|
||||
* @return Promise <Node[]>
|
||||
*/
|
||||
public function getReferenceNodesByNode(Node $node): Promise
|
||||
{
|
||||
return coroutine(function () use ($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 Variable
|
||||
|| $node instanceof Param
|
||||
|| $node instanceof ClosureUse
|
||||
) {
|
||||
if ($node->name instanceof \PhpParserNode\Expr) {
|
||||
return null;
|
||||
}
|
||||
// Find function/method/closure scope
|
||||
$n = $node;
|
||||
while (isset($n) && !($n instanceof 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->nodes;
|
||||
}
|
||||
// Definition with a global FQN
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
if ($fqn === null) {
|
||||
return [];
|
||||
}
|
||||
$refDocuments = yield $this->getReferenceDocuments($fqn);
|
||||
$nodes = [];
|
||||
foreach ($refDocuments as $document) {
|
||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||
if ($refs !== null) {
|
||||
foreach ($refs as $ref) {
|
||||
$nodes[] = $ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $nodes;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ class TextDocument
|
|||
*/
|
||||
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
||||
{
|
||||
$this->project->getDocument($textDocument->uri)->updateContent($contentChanges[0]->text);
|
||||
$this->project->openDocument($textDocument->uri, $contentChanges[0]->text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,7 +146,7 @@ class TextDocument
|
|||
if ($node === null) {
|
||||
return [];
|
||||
}
|
||||
$refs = yield $document->getReferenceNodesByNode($node);
|
||||
$refs = yield $this->project->getReferenceNodesByNode($node);
|
||||
$locations = [];
|
||||
foreach ($refs as $ref) {
|
||||
$locations[] = Location::fromNode($ref);
|
||||
|
|
|
@ -7,11 +7,6 @@ use PHPUnit\Framework\TestCase;
|
|||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentItem,
|
||||
TextDocumentIdentifier,
|
||||
SymbolKind,
|
||||
DiagnosticSeverity,
|
||||
FormattingOptions,
|
||||
ClientCapabilities
|
||||
};
|
||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
||||
|
|
|
@ -7,8 +7,6 @@ use PHPUnit\Framework\TestCase;
|
|||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\{Server, Client, LanguageClient, Project};
|
||||
use LanguageServer\Protocol\{
|
||||
TextDocumentIdentifier,
|
||||
TextDocumentItem,
|
||||
VersionedTextDocumentIdentifier,
|
||||
TextDocumentContentChangeEvent,
|
||||
Range,
|
||||
|
|
Loading…
Reference in New Issue