Merge branch 'fluent-interfaces' of github.com:vakata/php-language-server into fluent-interfaces
commit
51c0a94a21
|
@ -1,5 +1,4 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
|
||||||
.idea
|
.idea
|
||||||
vendor/
|
vendor/
|
||||||
.phpls/
|
.phpls/
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Listen for XDebug",
|
||||||
|
"type": "php",
|
||||||
|
"request": "launch",
|
||||||
|
"port": 9000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch currently open script",
|
||||||
|
"type": "php",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"cwd": "${fileDirname}",
|
||||||
|
"port": 9000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PHPUnit",
|
||||||
|
"type": "php",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceRoot}/vendor/phpunit/phpunit/phpunit",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"args": [
|
||||||
|
// "--filter", "CompletionTest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
"search.exclude": {
|
||||||
|
"**/validation": true,
|
||||||
|
"**/tests/Validation/cases": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@
|
||||||
A pure PHP implementation of the open [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).
|
A pure PHP implementation of the open [Language Server Protocol](https://github.com/Microsoft/language-server-protocol).
|
||||||
Provides static code analysis for PHP for any IDE.
|
Provides static code analysis for PHP for any IDE.
|
||||||
|
|
||||||
Uses the great [PHP-Parser](https://github.com/nikic/PHP-Parser),
|
Uses the great [Tolerant PHP Parser](https://github.com/Microsoft/tolerant-php-parser),
|
||||||
[phpDocumentor's DocBlock reflection](https://github.com/phpDocumentor/ReflectionDocBlock)
|
[phpDocumentor's DocBlock reflection](https://github.com/phpDocumentor/ReflectionDocBlock)
|
||||||
and an [event loop](http://sabre.io/event/loop/) for concurrency.
|
and an [event loop](http://sabre.io/event/loop/) for concurrency.
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ Example:
|
||||||
#### `--memory-limit=integer` (optional)
|
#### `--memory-limit=integer` (optional)
|
||||||
Sets memory limit for language server.
|
Sets memory limit for language server.
|
||||||
Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive.
|
Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive.
|
||||||
By default there is no memory limit.
|
The default is 4GB (which is way more than needed).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use Composer\{Factory, XdebugHandler};
|
||||||
|
|
||||||
$options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']);
|
$options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']);
|
||||||
|
|
||||||
ini_set('memory_limit', $options['memory-limit'] ?? -1);
|
ini_set('memory_limit', $options['memory-limit'] ?? '4G');
|
||||||
|
|
||||||
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
|
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
|
||||||
if (file_exists($file)) {
|
if (file_exists($file)) {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uniquely identifies a Composer package
|
||||||
|
*/
|
||||||
|
class PackageDescriptor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The package name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name The package name
|
||||||
|
*/
|
||||||
|
public function __construct(string $name = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,10 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\Protocol;
|
namespace LanguageServer\Protocol;
|
||||||
|
|
||||||
class SymbolDescriptor extends SymbolInformation
|
/**
|
||||||
|
* Uniquely identifies a symbol
|
||||||
|
*/
|
||||||
|
class SymbolDescriptor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The fully qualified structural element name, a globally unique identifier for the symbol.
|
* The fully qualified structural element name, a globally unique identifier for the symbol.
|
||||||
|
@ -13,19 +16,17 @@ class SymbolDescriptor extends SymbolInformation
|
||||||
public $fqsen;
|
public $fqsen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A package from the composer.lock file or the contents of the composer.json
|
* Identifies the Composer package the symbol is defined in (if any)
|
||||||
* Example: https://github.com/composer/composer/blob/master/composer.lock#L10
|
|
||||||
* Available fields may differ
|
|
||||||
*
|
*
|
||||||
* @var object|null
|
* @var PackageDescriptor|null
|
||||||
*/
|
*/
|
||||||
public $package;
|
public $package;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol.
|
* @param string $fqsen The fully qualified structural element name, a globally unique identifier for the symbol.
|
||||||
* @param object $package A package from the composer.lock file or the contents of the composer.json
|
* @param PackageDescriptor $package Identifies the Composer package the symbol is defined in
|
||||||
*/
|
*/
|
||||||
public function __construct(string $fqsen = null, $package = null)
|
public function __construct(string $fqsen = null, PackageDescriptor $package = null)
|
||||||
{
|
{
|
||||||
$this->fqsen = $fqsen;
|
$this->fqsen = $fqsen;
|
||||||
$this->package = $package;
|
$this->package = $package;
|
||||||
|
|
|
@ -8,14 +8,26 @@ use LanguageServer\{
|
||||||
};
|
};
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use LanguageServer\Protocol\{
|
use LanguageServer\Protocol\{
|
||||||
FormattingOptions, Hover, Location, MarkedString, Position, Range, ReferenceContext, SymbolDescriptor, SymbolLocationInformation, TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier
|
FormattingOptions,
|
||||||
|
Hover,
|
||||||
|
Location,
|
||||||
|
MarkedString,
|
||||||
|
Position,
|
||||||
|
Range,
|
||||||
|
ReferenceContext,
|
||||||
|
SymbolDescriptor,
|
||||||
|
PackageDescriptor,
|
||||||
|
SymbolLocationInformation,
|
||||||
|
TextDocumentIdentifier,
|
||||||
|
TextDocumentItem,
|
||||||
|
VersionedTextDocumentIdentifier
|
||||||
};
|
};
|
||||||
use Microsoft\PhpParser;
|
use Microsoft\PhpParser;
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use Sabre\Uri;
|
use Sabre\Uri;
|
||||||
use function LanguageServer\{
|
use function LanguageServer\{
|
||||||
isVendored, waitForEvent
|
isVendored, waitForEvent, getPackageName
|
||||||
};
|
};
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
|
@ -368,9 +380,10 @@ class TextDocument
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// Handle definition nodes
|
// Handle definition nodes
|
||||||
|
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||||
while (true) {
|
while (true) {
|
||||||
if ($fqn) {
|
if ($fqn) {
|
||||||
$def = $this->index->getDefinition($definedFqn);
|
$def = $this->index->getDefinition($fqn);
|
||||||
} else {
|
} else {
|
||||||
// Handle reference nodes
|
// Handle reference nodes
|
||||||
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node);
|
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($node);
|
||||||
|
@ -388,25 +401,14 @@ class TextDocument
|
||||||
) {
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$symbol = new SymbolDescriptor;
|
// if Definition is inside a dependency, use the package name
|
||||||
foreach (get_object_vars($def->symbolInformation) as $prop => $val) {
|
|
||||||
$symbol->$prop = $val;
|
|
||||||
}
|
|
||||||
$symbol->fqsen = $def->fqn;
|
|
||||||
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
||||||
if ($packageName && $this->composerLock !== null) {
|
// else use the package name of the root package (if exists)
|
||||||
// Definition is inside a dependency
|
if (!$packageName && $this->composerJson !== null) {
|
||||||
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
$packageName = $this->composerJson->name;
|
||||||
if ($package->name === $packageName) {
|
|
||||||
$symbol->package = $package;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ($this->composerJson !== null) {
|
|
||||||
// Definition belongs to a root package
|
|
||||||
$symbol->package = $this->composerJson;
|
|
||||||
}
|
}
|
||||||
return [new SymbolLocationInformation($symbol, $symbol->location)];
|
$descriptor = new SymbolDescriptor($def->fqn, new PackageDescriptor($packageName));
|
||||||
|
return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,15 @@ use LanguageServer\Protocol\{
|
||||||
FileEvent,
|
FileEvent,
|
||||||
SymbolInformation,
|
SymbolInformation,
|
||||||
SymbolDescriptor,
|
SymbolDescriptor,
|
||||||
|
PackageDescriptor,
|
||||||
ReferenceInformation,
|
ReferenceInformation,
|
||||||
DependencyReference,
|
DependencyReference,
|
||||||
Location
|
Location,
|
||||||
|
MessageType
|
||||||
};
|
};
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
use function LanguageServer\{waitForEvent, getPackageName};
|
use function LanguageServer\waitForEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all workspace/* methods
|
* Provides method handlers for all workspace/* methods
|
||||||
|
@ -33,7 +35,7 @@ class Workspace
|
||||||
*
|
*
|
||||||
* @var ProjectIndex
|
* @var ProjectIndex
|
||||||
*/
|
*/
|
||||||
private $index;
|
private $projectIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var DependenciesIndex
|
* @var DependenciesIndex
|
||||||
|
@ -57,17 +59,17 @@ class Workspace
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param LanguageClient $client LanguageClient instance used to signal updated results
|
* @param LanguageClient $client LanguageClient instance used to signal updated results
|
||||||
* @param ProjectIndex $index Index that is searched on a workspace/symbol request
|
* @param ProjectIndex $projectIndex Index that is used to wait for full index completeness
|
||||||
* @param DependenciesIndex $dependenciesIndex Index that is used on a workspace/xreferences request
|
* @param DependenciesIndex $dependenciesIndex Index that is used on a workspace/xreferences request
|
||||||
* @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request
|
* @param DependenciesIndex $sourceIndex Index that is used on a workspace/xreferences request
|
||||||
* @param \stdClass $composerLock The parsed composer.lock of the project, if any
|
* @param \stdClass $composerLock The parsed composer.lock of the project, if any
|
||||||
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents
|
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents
|
||||||
*/
|
*/
|
||||||
public function __construct(LanguageClient $client, ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null)
|
public function __construct(LanguageClient $client, ProjectIndex $projectIndex, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null)
|
||||||
{
|
{
|
||||||
$this->client = $client;
|
$this->client = $client;
|
||||||
$this->sourceIndex = $sourceIndex;
|
$this->sourceIndex = $sourceIndex;
|
||||||
$this->index = $index;
|
$this->projectIndex = $projectIndex;
|
||||||
$this->dependenciesIndex = $dependenciesIndex;
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
$this->composerLock = $composerLock;
|
$this->composerLock = $composerLock;
|
||||||
$this->documentLoader = $documentLoader;
|
$this->documentLoader = $documentLoader;
|
||||||
|
@ -84,11 +86,11 @@ class Workspace
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($query) {
|
return coroutine(function () use ($query) {
|
||||||
// Wait until indexing for definitions finished
|
// Wait until indexing for definitions finished
|
||||||
if (!$this->index->isStaticComplete()) {
|
if (!$this->sourceIndex->isStaticComplete()) {
|
||||||
yield waitForEvent($this->index, 'static-complete');
|
yield waitForEvent($this->sourceIndex, 'static-complete');
|
||||||
}
|
}
|
||||||
$symbols = [];
|
$symbols = [];
|
||||||
foreach ($this->index->getDefinitions() as $fqn => $definition) {
|
foreach ($this->sourceIndex->getDefinitions() as $fqn => $definition) {
|
||||||
if ($query === '' || stripos($fqn, $query) !== false) {
|
if ($query === '' || stripos($fqn, $query) !== false) {
|
||||||
$symbols[] = $definition->symbolInformation;
|
$symbols[] = $definition->symbolInformation;
|
||||||
}
|
}
|
||||||
|
@ -126,8 +128,8 @@ class Workspace
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// Wait until indexing finished
|
// Wait until indexing finished
|
||||||
if (!$this->index->isComplete()) {
|
if (!$this->projectIndex->isComplete()) {
|
||||||
yield waitForEvent($this->index, 'complete');
|
yield waitForEvent($this->projectIndex, 'complete');
|
||||||
}
|
}
|
||||||
/** Map from URI to array of referenced FQNs in dependencies */
|
/** Map from URI to array of referenced FQNs in dependencies */
|
||||||
$refs = [];
|
$refs = [];
|
||||||
|
@ -146,38 +148,11 @@ class Workspace
|
||||||
$refInfos = [];
|
$refInfos = [];
|
||||||
foreach ($refs as $uri => $fqns) {
|
foreach ($refs as $uri => $fqns) {
|
||||||
foreach ($fqns as $fqn) {
|
foreach ($fqns as $fqn) {
|
||||||
$def = $this->dependenciesIndex->getDefinition($fqn);
|
|
||||||
$symbol = new SymbolDescriptor;
|
|
||||||
$symbol->fqsen = $fqn;
|
|
||||||
foreach (get_object_vars($def->symbolInformation) as $prop => $val) {
|
|
||||||
$symbol->$prop = $val;
|
|
||||||
}
|
|
||||||
// Find out package name
|
|
||||||
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
|
||||||
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
|
||||||
if ($package->name === $packageName) {
|
|
||||||
$symbol->package = $package;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there was no FQSEN provided, check if query attributes match
|
|
||||||
if (!isset($query->fqsen)) {
|
|
||||||
$matches = true;
|
|
||||||
foreach (get_object_vars($query) as $prop => $val) {
|
|
||||||
if ($query->$prop != $symbol->$prop) {
|
|
||||||
$matches = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$matches) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$doc = yield $this->documentLoader->getOrLoad($uri);
|
$doc = yield $this->documentLoader->getOrLoad($uri);
|
||||||
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
|
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
|
||||||
$refInfo = new ReferenceInformation;
|
$refInfo = new ReferenceInformation;
|
||||||
$refInfo->reference = Location::fromNode($node);
|
$refInfo->reference = Location::fromNode($node);
|
||||||
$refInfo->symbol = $symbol;
|
$refInfo->symbol = $query;
|
||||||
$refInfos[] = $refInfo;
|
$refInfos[] = $refInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue