Dont like this
parent
dc3e4a6b81
commit
1e6bf90424
|
@ -32,8 +32,27 @@
|
|||
"netresearch/jsonmapper": "^1.0",
|
||||
"webmozart/path-util": "^2.3",
|
||||
"webmozart/glob": "^4.1",
|
||||
"sabre/uri": "^2.0"
|
||||
"sabre/uri": "^2.0",
|
||||
"JetBrains/phpstorm-stubs": "dev-master"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "JetBrains/phpstorm-stubs",
|
||||
"version": "dev-master",
|
||||
"dist": {
|
||||
"url": "https://github.com/JetBrains/phpstorm-stubs/archive/master.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "https://github.com/JetBrains/phpstorm-stubs",
|
||||
"type": "git",
|
||||
"reference": "master"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
|
|
|
@ -13,9 +13,9 @@ use function Sabre\Event\coroutine;
|
|||
class DefinitionResolver
|
||||
{
|
||||
/**
|
||||
* @var \LanguageServer\Project
|
||||
* @var \LanguageServer\Index[]
|
||||
*/
|
||||
private $project;
|
||||
private $indexes;
|
||||
|
||||
/**
|
||||
* @var \phpDocumentor\Reflection\TypeResolver
|
||||
|
@ -27,13 +27,25 @@ class DefinitionResolver
|
|||
*/
|
||||
private $prettyPrinter;
|
||||
|
||||
public function __construct(Project $project)
|
||||
/**
|
||||
* @param Index[] $indexes
|
||||
*/
|
||||
public function __construct(array $indexes)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->indexes = $indexes;
|
||||
$this->typeResolver = new TypeResolver;
|
||||
$this->prettyPrinter = new PrettyPrinter;
|
||||
}
|
||||
|
||||
private function getDefinition(string $fqn, bool $globalFallback = false)
|
||||
{
|
||||
foreach ($this->indexes as $index) {
|
||||
if ($def = $index->getDefinition($fqn, $globalFallback)) {
|
||||
return $def;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the declaration line for a given node
|
||||
*
|
||||
|
@ -147,8 +159,8 @@ class DefinitionResolver
|
|||
// http://php.net/manual/en/language.namespaces.fallback.php
|
||||
$parent = $node->getAttribute('parentNode');
|
||||
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
|
||||
// Return the Definition object from the project index
|
||||
return $this->project->getDefinition($fqn, $globalFallback);
|
||||
// Return the Definition object from the index index
|
||||
return $this->getDefinition($fqn, $globalFallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -403,7 +415,7 @@ class DefinitionResolver
|
|||
return new Types\Mixed;
|
||||
}
|
||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||
$def = $this->project->getDefinition($fqn, true);
|
||||
$def = $this->getDefinition($fqn, true);
|
||||
if ($def !== null) {
|
||||
return $def->type;
|
||||
}
|
||||
|
@ -414,7 +426,7 @@ class DefinitionResolver
|
|||
}
|
||||
// Resolve constant
|
||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||
$def = $this->project->getDefinition($fqn, true);
|
||||
$def = $this->getDefinition($fqn, true);
|
||||
if ($def !== null) {
|
||||
return $def->type;
|
||||
}
|
||||
|
@ -443,7 +455,7 @@ class DefinitionResolver
|
|||
if ($expr instanceof Node\Expr\MethodCall) {
|
||||
$fqn .= '()';
|
||||
}
|
||||
$def = $this->project->getDefinition($fqn);
|
||||
$def = $this->getDefinition($fqn);
|
||||
if ($def !== null) {
|
||||
return $def->type;
|
||||
}
|
||||
|
@ -466,7 +478,7 @@ class DefinitionResolver
|
|||
if ($expr instanceof Node\Expr\StaticCall) {
|
||||
$fqn .= '()';
|
||||
}
|
||||
$def = $this->project->getDefinition($fqn);
|
||||
$def = $this->getDefinition($fqn);
|
||||
if ($def === null) {
|
||||
return new Types\Mixed;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer;
|
||||
|
||||
class PhpDocumentLoader
|
||||
{
|
||||
public function __construct(ContentRetriever $contentRetriever)
|
||||
{
|
||||
$this->contentRetriever = $contentRetriever;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a document
|
||||
*
|
||||
* @param string $uri
|
||||
* @return Promise <PhpDocument>
|
||||
*/
|
||||
public function load(string $uri): Promise
|
||||
{
|
||||
return coroutine(function () use ($uri) {
|
||||
|
||||
$limit = 150000;
|
||||
$content = yield $this->contentRetriever->retrieve($uri);
|
||||
$size = strlen($content);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
|
||||
/** The key for the index */
|
||||
$key = '';
|
||||
|
||||
// If the document is part of a dependency
|
||||
if (preg_match($u['path'], '/vendor\/(\w+\/\w+)/', $matches)) {
|
||||
if ($this->composerLockFiles === null) {
|
||||
throw new \Exception('composer.lock files were not read yet');
|
||||
}
|
||||
// Try to find closest composer.lock
|
||||
$u = Uri\parse($uri);
|
||||
$packageName = $matches[1];
|
||||
do {
|
||||
$u['path'] = dirname($u['path']);
|
||||
foreach ($this->composerLockFiles as $lockFileUri => $lockFileContent) {
|
||||
$lockFileUri = Uri\parse($composerLockFile);
|
||||
$lockFileUri['path'] = dirname($lockFileUri['path']);
|
||||
if ($u == $lockFileUri) {
|
||||
// Found it, find out package version
|
||||
foreach ($lockFileContent->packages as $package) {
|
||||
if ($package->name === $packageName) {
|
||||
$key = $packageName . ':' . $package->version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!empty(trim($u, '/')));
|
||||
}
|
||||
|
||||
// If there is no index for the key yet, create one
|
||||
if (!isset($this->indexes[$key])) {
|
||||
$this->indexes[$key] = new Index;
|
||||
}
|
||||
$index = $this->indexes[$key];
|
||||
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument(
|
||||
$uri,
|
||||
$content,
|
||||
$index,
|
||||
$this->parser,
|
||||
$this->docBlockFactory,
|
||||
$this->definitionResolver
|
||||
);
|
||||
}
|
||||
return $document;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -150,15 +150,4 @@ class Index
|
|||
{
|
||||
$this->references = $references;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given FQN is defined in the project
|
||||
*
|
||||
* @param string $fqn The fully qualified name of the symbol
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefined(string $fqn): bool
|
||||
{
|
||||
return isset($this->definitions[$fqn]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use function Sabre\Event\coroutine;
|
|||
use Exception;
|
||||
use Throwable;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Webmozart\Glob\Glob;
|
||||
use Sabre\Uri;
|
||||
|
||||
class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||
|
@ -92,7 +93,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
} catch (Throwable $e) {
|
||||
// If an unexpected error occured, send back an INTERNAL_ERROR error response
|
||||
$error = new AdvancedJsonRpc\Error(
|
||||
$e->getMessage(),
|
||||
(string)$e,
|
||||
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
|
||||
null,
|
||||
$e
|
||||
|
@ -120,9 +121,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
* @param ClientCapabilities $capabilities The capabilities provided by the client (editor)
|
||||
* @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open.
|
||||
* @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process.
|
||||
* @return InitializeResult
|
||||
* @return Promise <InitializeResult>
|
||||
*/
|
||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): InitializeResult
|
||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): Promise
|
||||
{
|
||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
||||
|
||||
|
@ -140,11 +141,10 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||
$this->contentRetriever = new FileSystemContentRetriever;
|
||||
}
|
||||
|
||||
// start building project index
|
||||
if ($rootPath !== null) {
|
||||
$pattern = Path::makeAbsolute('**/{*.php,composer.lock}', $this->rootPath);
|
||||
$composerLockPattern = Path::makeAbsolute('**/composer.lock}', $this->rootPath);
|
||||
$uris = yield $this->findFiles($pattern);
|
||||
$uris = yield $this->filesFinder->find($pattern);
|
||||
|
||||
// Find composer.lock files
|
||||
$composerLockFiles = [];
|
||||
|
|
|
@ -22,22 +22,6 @@ 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
|
||||
*
|
||||
|
@ -101,28 +85,29 @@ class PhpDocument
|
|||
*/
|
||||
private $referenceNodes;
|
||||
|
||||
/**
|
||||
* Diagnostics for this document that were collected while parsing
|
||||
*
|
||||
* @var Diagnostic[]
|
||||
*/
|
||||
private $diagnostics;
|
||||
|
||||
/**
|
||||
* @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 load other documents)
|
||||
* @param Index $index The Index 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,
|
||||
Index $index,
|
||||
LanguageClient $client,
|
||||
Parser $parser,
|
||||
DocBlockFactory $docBlockFactory,
|
||||
DefinitionResolver $definitionResolver
|
||||
) {
|
||||
$this->uri = $uri;
|
||||
$this->project = $project;
|
||||
$this->client = $client;
|
||||
$this->parser = $parser;
|
||||
$this->docBlockFactory = $docBlockFactory;
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
|
@ -156,9 +141,9 @@ class PhpDocument
|
|||
$errorHandler = new ErrorHandler\Collecting;
|
||||
$stmts = $this->parser->parse($content, $errorHandler);
|
||||
|
||||
$diagnostics = [];
|
||||
$this->diagnostics = [];
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
||||
$this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php');
|
||||
}
|
||||
|
||||
// $stmts can be null in case of a fatal parsing error
|
||||
|
@ -182,7 +167,7 @@ class PhpDocument
|
|||
|
||||
// Report errors from parsing docblocks
|
||||
foreach ($docBlockParser->errors as $error) {
|
||||
$diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php');
|
||||
$this->diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php');
|
||||
}
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
|
@ -224,10 +209,6 @@ class PhpDocument
|
|||
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
if (!$this->isVendored()) {
|
||||
$this->client->textDocument->publishDiagnostics($this->uri, $diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,6 +245,16 @@ class PhpDocument
|
|||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this document's diagnostics
|
||||
*
|
||||
* @return Diagnostic[]
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->diagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the document
|
||||
*
|
||||
|
@ -359,57 +350,4 @@ 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
|
||||
{
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
175
src/Project.php
175
src/Project.php
|
@ -21,11 +21,24 @@ class Project
|
|||
|
||||
/**
|
||||
* Associative array from package identifier to index
|
||||
* The empty string represents the project itself
|
||||
*
|
||||
* @var Index[]
|
||||
*/
|
||||
private $indexes = [];
|
||||
private $dependencyIndexes = [];
|
||||
|
||||
/**
|
||||
* The Index for the project itself
|
||||
*
|
||||
* @var Index
|
||||
*/
|
||||
private $sourceIndex;
|
||||
|
||||
/**
|
||||
* The Index for PHP built-ins
|
||||
*
|
||||
* @var Index
|
||||
*/
|
||||
private $stubIndex;
|
||||
|
||||
/**
|
||||
* An associative array that maps fully qualified symbol names to Definitions
|
||||
|
@ -84,23 +97,24 @@ class Project
|
|||
* @param LanguageClient $client Used for logging and reporting diagnostics
|
||||
* @param ClientCapabilities $clientCapabilities Used for determining the right content/find strategies
|
||||
* @param string|null $rootPath Used for finding files in the project
|
||||
* @param string[] $composerLockFiles An array of URIs of composer.lock files in the project
|
||||
* @param string $composerLockFiles An array of URI => parsed composer.lock JSON
|
||||
*/
|
||||
public function __construct(
|
||||
LanguageClient $client,
|
||||
ClientCapabilities $clientCapabilities,
|
||||
array $composerLockFiles,
|
||||
DefinitionResolver $definitionResolver,
|
||||
string $rootPath = null
|
||||
) {
|
||||
$this->client = $client;
|
||||
$this->rootPath = $rootPath;
|
||||
$this->parser = new Parser;
|
||||
$this->docBlockFactory = DocBlockFactory::createInstance();
|
||||
$this->definitionResolver = new DefinitionResolver($this);
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
$this->contentRetriever = $contentRetriever;
|
||||
$this->composerLockFiles = $composerLockFiles;
|
||||
// The index for the project itself
|
||||
$this->indexes[''] = new Index;
|
||||
$this->projectIndex = new Index;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,80 +141,6 @@ class Project
|
|||
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;
|
||||
$content = yield $this->contentRetriever->retrieve($uri);
|
||||
$size = strlen($content);
|
||||
if ($size > $limit) {
|
||||
throw new ContentTooLargeException($uri, $size, $limit);
|
||||
}
|
||||
|
||||
/** The key for the index */
|
||||
$key = '';
|
||||
|
||||
// If the document is part of a dependency
|
||||
if (preg_match($u['path'], '/vendor\/(\w+\/\w+)/', $matches)) {
|
||||
if ($this->composerLockFiles === null) {
|
||||
throw new \Exception('composer.lock files were not read yet');
|
||||
}
|
||||
// Try to find closest composer.lock
|
||||
$u = Uri\parse($uri);
|
||||
$packageName = $matches[1];
|
||||
do {
|
||||
$u['path'] = dirname($u['path']);
|
||||
foreach ($this->composerLockFiles as $lockFileUri => $lockFileContent) {
|
||||
$lockFileUri = Uri\parse($composerLockFile);
|
||||
$lockFileUri['path'] = dirname($lockFileUri['path']);
|
||||
if ($u == $lockFileUri) {
|
||||
// Found it, find out package version
|
||||
foreach ($lockFileContent->packages as $package) {
|
||||
if ($package->name === $packageName) {
|
||||
$key = $packageName . ':' . $package->version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!empty(trim($u, '/')));
|
||||
}
|
||||
|
||||
// If there is no index for the key yet, create one
|
||||
if (!isset($this->indexes[$key])) {
|
||||
$this->indexes[$key] = new Index;
|
||||
}
|
||||
$index = $this->indexes[$key];
|
||||
|
||||
if (isset($this->documents[$uri])) {
|
||||
$document = $this->documents[$uri];
|
||||
$document->updateContent($content);
|
||||
} else {
|
||||
$document = new PhpDocument(
|
||||
$uri,
|
||||
$content,
|
||||
$this,
|
||||
$index,
|
||||
$this->client,
|
||||
$this->parser,
|
||||
$this->docBlockFactory,
|
||||
$this->definitionResolver
|
||||
);
|
||||
}
|
||||
return $document;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a document is loaded and added to the list of open documents.
|
||||
*
|
||||
|
@ -217,8 +157,6 @@ class Project
|
|||
$document = new PhpDocument(
|
||||
$uri,
|
||||
$content,
|
||||
$this,
|
||||
$this->client,
|
||||
$this->parser,
|
||||
$this->docBlockFactory,
|
||||
$this->definitionResolver
|
||||
|
@ -258,7 +196,19 @@ class Project
|
|||
*/
|
||||
public function getDefinitions()
|
||||
{
|
||||
return $this->definitions;
|
||||
$defs = [];
|
||||
foreach ($this->sourceIndex->getDefinitions() as $def) {
|
||||
$defs[] = $def;
|
||||
}
|
||||
foreach ($this->dependenciesIndexes as $dependencyIndex) {
|
||||
foreach ($dependencyIndex->getDefinitions() as $def) {
|
||||
$defs[] = $def;
|
||||
}
|
||||
}
|
||||
foreach ($this->stubIndex->getDefinitions() as $def) {
|
||||
$defs[] = $def;
|
||||
}
|
||||
return $defs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -270,9 +220,12 @@ class Project
|
|||
*/
|
||||
public function getDefinition(string $fqn, $globalFallback = false)
|
||||
{
|
||||
if (isset($this->definitions[$fqn])) {
|
||||
return $this->definitions[$fqn];
|
||||
} else if ($globalFallback) {
|
||||
foreach (array_merge([$this->sourceIndex, $this->stubsIndex], ...$this->dependencyIndexes) as $index) {
|
||||
if ($index->isDefined($fqn)) {
|
||||
return $index->getDefinition($fqn);
|
||||
}
|
||||
}
|
||||
if ($globalFallback) {
|
||||
$parts = explode('\\', $fqn);
|
||||
$fqn = end($parts);
|
||||
return $this->getDefinition($fqn);
|
||||
|
@ -411,4 +364,56 @@ class Project
|
|||
{
|
||||
return isset($this->definitions[$fqn]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference nodes for any node
|
||||
*
|
||||
* @param Node $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 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->getReferenceDocuments($fqn);
|
||||
$nodes = [];
|
||||
foreach ($refDocuments as $document) {
|
||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||
if ($refs !== null) {
|
||||
foreach ($refs as $ref) {
|
||||
$nodes[] = $ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $nodes;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,12 +58,14 @@ class TextDocument
|
|||
*/
|
||||
private $completionProvider;
|
||||
|
||||
private $openDocuments = [];
|
||||
|
||||
public function __construct(Project $project, LanguageClient $client)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->client = $client;
|
||||
$this->prettyPrinter = new PrettyPrinter();
|
||||
$this->definitionResolver = new DefinitionResolver($project);
|
||||
$this->definitionResolver = new DefinitionResolver();
|
||||
$this->completionProvider = new CompletionProvider($this->definitionResolver, $project);
|
||||
}
|
||||
|
||||
|
@ -95,7 +97,10 @@ class TextDocument
|
|||
*/
|
||||
public function didOpen(TextDocumentItem $textDocument)
|
||||
{
|
||||
$this->project->openDocument($textDocument->uri, $textDocument->text);
|
||||
$document = $this->project->openDocument($textDocument->uri, $textDocument->text);
|
||||
if (!$document->isVendored()) {
|
||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +112,9 @@ class TextDocument
|
|||
*/
|
||||
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
||||
{
|
||||
$this->project->getDocument($textDocument->uri)->updateContent($contentChanges[0]->text);
|
||||
$document = $this->project->getDocument($textDocument->uri);
|
||||
$document->updateContent($contentChanges[0]->text);
|
||||
$this->client->publishDiagnostics($document->getDiagnostics());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace LanguageServer;
|
||||
|
||||
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use function Sabre\Event\coroutine;
|
||||
|
||||
coroutine(function () {
|
||||
|
||||
$index = new Index;
|
||||
|
||||
$finder = new FileSystemFilesFinder;
|
||||
$contentRetriever = new FileSystemContentRetriever;
|
||||
$docBlockFactory = DocBlockFactory::createInstance();
|
||||
$definitionResolver = new DefinitionResolver([$index]);
|
||||
|
||||
$uris = yield $finder->find(__DIR__ . '/../vendor/JetBrains/phpstorm-stubs');
|
||||
|
||||
foreach ($uris as $uri) {
|
||||
$content = $contentRetriever->retrieve($uri);
|
||||
$document = new PhpDocument($uri, $content, $index, $docBlockFactory, $definitionResolver);
|
||||
}
|
||||
|
||||
})->wait();
|
Loading…
Reference in New Issue