Merge a5433b211a
into 9dc1656592
commit
9667be3278
|
@ -283,9 +283,14 @@ return [
|
||||||
// your application should be included in this list.
|
// your application should be included in this list.
|
||||||
'directory_list' => [
|
'directory_list' => [
|
||||||
'src',
|
'src',
|
||||||
|
'vendor/amphp/amp/lib',
|
||||||
|
'vendor/amphp/file/lib',
|
||||||
|
'vendor/amphp/socket/src',
|
||||||
'vendor/composer/xdebug-handler/src',
|
'vendor/composer/xdebug-handler/src',
|
||||||
'vendor/felixfbecker/advanced-json-rpc/lib',
|
'vendor/felixfbecker/advanced-json-rpc/lib',
|
||||||
'vendor/felixfbecker/language-server-protocol/src/',
|
'vendor/felixfbecker/language-server-protocol/src/',
|
||||||
|
'vendor/league/event/src',
|
||||||
|
'vendor/league/uri-parser/src',
|
||||||
'vendor/microsoft/tolerant-php-parser/src',
|
'vendor/microsoft/tolerant-php-parser/src',
|
||||||
'vendor/netresearch/jsonmapper/src',
|
'vendor/netresearch/jsonmapper/src',
|
||||||
'vendor/phpdocumentor/reflection-common/src',
|
'vendor/phpdocumentor/reflection-common/src',
|
||||||
|
@ -294,7 +299,6 @@ return [
|
||||||
'vendor/phpunit/phpunit/src',
|
'vendor/phpunit/phpunit/src',
|
||||||
'vendor/psr/log/Psr',
|
'vendor/psr/log/Psr',
|
||||||
'vendor/sabre/event/lib',
|
'vendor/sabre/event/lib',
|
||||||
'vendor/sabre/uri/lib',
|
|
||||||
'vendor/webmozart/glob/src',
|
'vendor/webmozart/glob/src',
|
||||||
'vendor/webmozart/path-util/src',
|
'vendor/webmozart/path-util/src',
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,7 +2,9 @@ language: php
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- '7.0'
|
- '7.0'
|
||||||
|
- '7.1'
|
||||||
- '7.2'
|
- '7.2'
|
||||||
|
- '7.3'
|
||||||
|
|
||||||
git:
|
git:
|
||||||
depth: 10
|
depth: 10
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter, StderrLogger};
|
use Amp\ByteStream\ResourceInputStream;
|
||||||
use Sabre\Event\Loop;
|
use Amp\ByteStream\ResourceOutputStream;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Socket\ClientSocket;
|
||||||
|
use Amp\Socket\ServerSocket;
|
||||||
use Composer\XdebugHandler\XdebugHandler;
|
use Composer\XdebugHandler\XdebugHandler;
|
||||||
|
use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter, StderrLogger};
|
||||||
|
|
||||||
$options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']);
|
$options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']);
|
||||||
|
|
||||||
|
@ -42,69 +46,51 @@ unset($xdebugHandler);
|
||||||
if (!empty($options['tcp'])) {
|
if (!empty($options['tcp'])) {
|
||||||
// Connect to a TCP server
|
// Connect to a TCP server
|
||||||
$address = $options['tcp'];
|
$address = $options['tcp'];
|
||||||
$socket = stream_socket_client('tcp://' . $address, $errno, $errstr);
|
$server = function () use ($logger, $address) {
|
||||||
if ($socket === false) {
|
/** @var ClientSocket $socket */
|
||||||
$logger->critical("Could not connect to language client. Error $errno\n$errstr");
|
$socket = yield Amp\Socket\connect('tcp://' . $address);
|
||||||
exit(1);
|
$ls = new LanguageServer(
|
||||||
}
|
new ProtocolStreamReader($socket),
|
||||||
stream_set_blocking($socket, false);
|
new ProtocolStreamWriter($socket)
|
||||||
$ls = new LanguageServer(
|
);
|
||||||
new ProtocolStreamReader($socket),
|
yield $ls->getshutdownDeferred();
|
||||||
new ProtocolStreamWriter($socket)
|
};
|
||||||
);
|
|
||||||
Loop\run();
|
|
||||||
} else if (!empty($options['tcp-server'])) {
|
} else if (!empty($options['tcp-server'])) {
|
||||||
// Run a TCP Server
|
// Run a TCP Server
|
||||||
$address = $options['tcp-server'];
|
$address = $options['tcp-server'];
|
||||||
$tcpServer = stream_socket_server('tcp://' . $address, $errno, $errstr);
|
$server = function () use ($logger, $address) {
|
||||||
if ($tcpServer === false) {
|
|
||||||
$logger->critical("Could not listen on $address. Error $errno\n$errstr");
|
$server = Amp\Socket\listen('tcp://' . $address);
|
||||||
exit(1);
|
|
||||||
}
|
$logger->debug("Server listening on $address");
|
||||||
$logger->debug("Server listening on $address");
|
|
||||||
$pcntlAvailable = extension_loaded('pcntl');
|
while ($socket = yield $server->accept()) {
|
||||||
if (!$pcntlAvailable) {
|
/** @var ServerSocket $socket */
|
||||||
$logger->notice('PCNTL is not available. Only a single connection will be accepted');
|
list($ip, $port) = \explode(':', $socket->getRemoteAddress());
|
||||||
}
|
|
||||||
while ($socket = stream_socket_accept($tcpServer, -1)) {
|
$logger->debug("Accepted connection from {$ip}:{$port}." . PHP_EOL);
|
||||||
$logger->debug('Connection accepted');
|
|
||||||
stream_set_blocking($socket, false);
|
Loop::run(function () use ($socket) {
|
||||||
if ($pcntlAvailable) {
|
$ls = new LanguageServer(
|
||||||
// If PCNTL is available, fork a child process for the connection
|
new ProtocolStreamReader($socket),
|
||||||
// An exit notification will only terminate the child process
|
new ProtocolStreamWriter($socket)
|
||||||
$pid = pcntl_fork();
|
);
|
||||||
if ($pid === -1) {
|
yield $ls->getshutdownDeferred();
|
||||||
$logger->critical('Could not fork');
|
});
|
||||||
exit(1);
|
|
||||||
} else if ($pid === 0) {
|
|
||||||
// Child process
|
|
||||||
$reader = new ProtocolStreamReader($socket);
|
|
||||||
$writer = new ProtocolStreamWriter($socket);
|
|
||||||
$reader->on('close', function () use ($logger) {
|
|
||||||
$logger->debug('Connection closed');
|
|
||||||
});
|
|
||||||
$ls = new LanguageServer($reader, $writer);
|
|
||||||
Loop\run();
|
|
||||||
// Just for safety
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If PCNTL is not available, we only accept one connection.
|
|
||||||
// An exit notification will terminate the server
|
|
||||||
$ls = new LanguageServer(
|
|
||||||
new ProtocolStreamReader($socket),
|
|
||||||
new ProtocolStreamWriter($socket)
|
|
||||||
);
|
|
||||||
Loop\run();
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
// Use STDIO
|
// Use STDIO
|
||||||
$logger->debug('Listening on STDIN');
|
$logger->debug('Listening on STDIN');
|
||||||
stream_set_blocking(STDIN, false);
|
$inputStream = new ResourceInputStream(STDIN);
|
||||||
|
$outputStream = new ResourceOutputStream(STDOUT);
|
||||||
$ls = new LanguageServer(
|
$ls = new LanguageServer(
|
||||||
new ProtocolStreamReader(STDIN),
|
new ProtocolStreamReader($inputStream),
|
||||||
new ProtocolStreamWriter(STDOUT)
|
new ProtocolStreamWriter($outputStream)
|
||||||
);
|
);
|
||||||
Loop\run();
|
$server = function () use ($ls) {
|
||||||
|
yield $ls->getshutdownDeferred();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loop::run($server);
|
||||||
|
|
|
@ -22,16 +22,21 @@
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.0",
|
"php": "^7.0",
|
||||||
|
"amphp/byte-stream": "^1.5",
|
||||||
|
"amphp/cache": "^1.2",
|
||||||
|
"amphp/file": "^0.3.5",
|
||||||
|
"amphp/socket": "^0.10.11",
|
||||||
"composer/xdebug-handler": "^1.0",
|
"composer/xdebug-handler": "^1.0",
|
||||||
"felixfbecker/advanced-json-rpc": "^3.0.0",
|
"felixfbecker/advanced-json-rpc": "^3.0.0",
|
||||||
"felixfbecker/language-server-protocol": "^1.0.1",
|
"felixfbecker/language-server-protocol": "^1.0.1",
|
||||||
"jetbrains/phpstorm-stubs": "dev-master",
|
"jetbrains/phpstorm-stubs": "dev-master",
|
||||||
|
"league/event": "^2.2",
|
||||||
|
"league/uri-parser": "^1.4",
|
||||||
"microsoft/tolerant-php-parser": "0.0.*",
|
"microsoft/tolerant-php-parser": "0.0.*",
|
||||||
"netresearch/jsonmapper": "^1.0",
|
"netresearch/jsonmapper": "^1.0",
|
||||||
|
"php-ds/php-ds": "^1.2",
|
||||||
"phpdocumentor/reflection-docblock": "^4.0.0",
|
"phpdocumentor/reflection-docblock": "^4.0.0",
|
||||||
"psr/log": "^1.0",
|
"psr/log": "^1.0",
|
||||||
"sabre/event": "^5.0",
|
|
||||||
"sabre/uri": "^2.0",
|
|
||||||
"webmozart/glob": "^4.1",
|
"webmozart/glob": "^4.1",
|
||||||
"webmozart/path-util": "^2.3"
|
"webmozart/path-util": "^2.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Cache;
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A key/value store for caching purposes
|
* A key/value store for caching purposes
|
||||||
*/
|
*/
|
||||||
|
@ -14,16 +12,16 @@ interface Cache
|
||||||
* Gets a value from the cache
|
* Gets a value from the cache
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return Promise <mixed>
|
* @return \Generator <mixed>
|
||||||
*/
|
*/
|
||||||
public function get(string $key): Promise;
|
public function get(string $key): \Generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a value in the cache
|
* Sets a value in the cache
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return Promise
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
public function set(string $key, $value): Promise;
|
public function set(string $key, $value): \Generator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Cache;
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
|
@ -30,24 +30,22 @@ class ClientCache implements Cache
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return Promise <mixed>
|
* @return Promise <mixed>
|
||||||
*/
|
*/
|
||||||
public function get(string $key): Promise
|
public function get(string $key): \Generator
|
||||||
{
|
{
|
||||||
return $this->client->xcache->get($key)->then('unserialize')->otherwise(function () {
|
$cached = yield from $this->client->xcache->get($key);
|
||||||
// Ignore
|
$obj = unserialize($cached);
|
||||||
});
|
return $obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a value in the cache
|
* Sets a value in the cache
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function set(string $key, $value): Promise
|
public function set(string $key, $value): \Generator
|
||||||
{
|
{
|
||||||
return $this->client->xcache->set($key, serialize($value))->otherwise(function () {
|
return yield from $this->client->xcache->set($key, serialize($value));
|
||||||
// Ignore
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Cache;
|
namespace LanguageServer\Cache;
|
||||||
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caches content on the file system
|
* Caches content on the file system
|
||||||
*/
|
*/
|
||||||
|
@ -30,18 +28,16 @@ class FileSystemCache implements Cache
|
||||||
* Gets a value from the cache
|
* Gets a value from the cache
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return Promise <mixed>
|
* @return \Generator <mixed>
|
||||||
*/
|
*/
|
||||||
public function get(string $key): Promise
|
public function get(string $key): \Generator
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$file = $this->cacheDir . urlencode($key);
|
$file = $this->cacheDir . urlencode($key);
|
||||||
if (!file_exists($file)) {
|
$content = yield \Amp\File\get($file);
|
||||||
return Promise\resolve(null);
|
return unserialize($content);
|
||||||
}
|
|
||||||
return Promise\resolve(unserialize(file_get_contents($file)));
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return Promise\resolve(null);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,19 +45,19 @@ class FileSystemCache implements Cache
|
||||||
* Sets a value in the cache
|
* Sets a value in the cache
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return Promise
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
public function set(string $key, $value): Promise
|
public function set(string $key, $value): \Generator
|
||||||
{
|
{
|
||||||
try {
|
$file = $this->cacheDir . urlencode($key);
|
||||||
$file = $this->cacheDir . urlencode($key);
|
$dir = dirname($file);
|
||||||
if (!file_exists($this->cacheDir)) {
|
if (yield \Amp\File\isfile($dir)) {
|
||||||
mkdir($this->cacheDir);
|
yield \Amp\File\unlink($dir);
|
||||||
}
|
|
||||||
file_put_contents($file, serialize($value));
|
|
||||||
} finally {
|
|
||||||
return Promise\resolve(null);
|
|
||||||
}
|
}
|
||||||
|
if (!yield \Amp\File\exists($dir)) {
|
||||||
|
yield \Amp\File\mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
yield \Amp\File\put($file, serialize($value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Client;
|
namespace LanguageServer\Client;
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ class TextDocument
|
||||||
* @param Diagnostic[] $diagnostics
|
* @param Diagnostic[] $diagnostics
|
||||||
* @return Promise <void>
|
* @return Promise <void>
|
||||||
*/
|
*/
|
||||||
public function publishDiagnostics(string $uri, array $diagnostics): Promise
|
public function publishDiagnostics(string $uri, array $diagnostics): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->notify('textDocument/publishDiagnostics', [
|
yield from $this->handler->notify('textDocument/publishDiagnostics', [
|
||||||
'uri' => $uri,
|
'uri' => $uri,
|
||||||
'diagnostics' => $diagnostics
|
'diagnostics' => $diagnostics
|
||||||
]);
|
]);
|
||||||
|
@ -51,13 +51,12 @@ class TextDocument
|
||||||
* @param TextDocumentIdentifier $textDocument The document to get the content for
|
* @param TextDocumentIdentifier $textDocument The document to get the content for
|
||||||
* @return Promise <TextDocumentItem> The document's current content
|
* @return Promise <TextDocumentItem> The document's current content
|
||||||
*/
|
*/
|
||||||
public function xcontent(TextDocumentIdentifier $textDocument): Promise
|
public function xcontent(TextDocumentIdentifier $textDocument): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->request(
|
$result = yield from $this->handler->request(
|
||||||
'textDocument/xcontent',
|
'textDocument/xcontent',
|
||||||
['textDocument' => $textDocument]
|
['textDocument' => $textDocument]
|
||||||
)->then(function ($result) {
|
);
|
||||||
return $this->mapper->map($result, new TextDocumentItem);
|
return $this->mapper->map($result, new TextDocumentItem);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,8 @@ class Window
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @return Promise <void>
|
* @return Promise <void>
|
||||||
*/
|
*/
|
||||||
public function logMessage(int $type, string $message): Promise
|
public function logMessage(int $type, string $message): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->notify('window/logMessage', ['type' => $type, 'message' => $message]);
|
yield from $this->handler->notify('window/logMessage', ['type' => $type, 'message' => $message]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Client;
|
namespace LanguageServer\Client;
|
||||||
|
|
||||||
|
@ -35,13 +35,12 @@ class Workspace
|
||||||
* @param string $base The base directory (defaults to the workspace)
|
* @param string $base The base directory (defaults to the workspace)
|
||||||
* @return Promise <TextDocumentIdentifier[]> Array of documents
|
* @return Promise <TextDocumentIdentifier[]> Array of documents
|
||||||
*/
|
*/
|
||||||
public function xfiles(string $base = null): Promise
|
public function xfiles(string $base = null): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->request(
|
$textDocuments = yield from $this->handler->request(
|
||||||
'workspace/xfiles',
|
'workspace/xfiles',
|
||||||
['base' => $base]
|
['base' => $base]
|
||||||
)->then(function (array $textDocuments) {
|
);
|
||||||
return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class);
|
return $this->mapper->mapArray($textDocuments, [], TextDocumentIdentifier::class);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Client;
|
namespace LanguageServer\Client;
|
||||||
|
|
||||||
|
@ -25,18 +25,18 @@ class XCache
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return Promise <mixed>
|
* @return Promise <mixed>
|
||||||
*/
|
*/
|
||||||
public function get(string $key): Promise
|
public function get(string $key): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->request('xcache/get', ['key' => $key]);
|
return yield from $this->handler->request('xcache/get', ['key' => $key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return Promise <mixed>
|
* @return Promise <mixed>
|
||||||
*/
|
*/
|
||||||
public function set(string $key, $value): Promise
|
public function set(string $key, $value): \Generator
|
||||||
{
|
{
|
||||||
return $this->handler->notify('xcache/set', ['key' => $key, 'value' => $value]);
|
return yield from $this->handler->notify('xcache/set', ['key' => $key, 'value' => $value]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Sabre\Event\Promise;
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
|
use LanguageServer\Event\MessageEvent;
|
||||||
|
|
||||||
class ClientHandler
|
class ClientHandler
|
||||||
{
|
{
|
||||||
|
@ -35,31 +37,35 @@ class ClientHandler
|
||||||
*
|
*
|
||||||
* @param string $method The method to call
|
* @param string $method The method to call
|
||||||
* @param array|object $params The method parameters
|
* @param array|object $params The method parameters
|
||||||
* @return Promise <mixed> Resolved with the result of the request or rejected with an error
|
* @return \Generator <mixed> Resolved with the result of the request or rejected with an error
|
||||||
*/
|
*/
|
||||||
public function request(string $method, $params): Promise
|
public function request(string $method, $params): \Generator
|
||||||
{
|
{
|
||||||
$id = $this->idGenerator->generate();
|
$id = $this->idGenerator->generate();
|
||||||
return $this->protocolWriter->write(
|
$deferred = new Deferred();
|
||||||
new Message(
|
$listener = function (MessageEvent $messageEvent) use ($id, $deferred, &$listener) {
|
||||||
new AdvancedJsonRpc\Request($id, $method, (object)$params)
|
$msg = $messageEvent->getMessage();
|
||||||
)
|
Loop::defer(function () use (&$listener, $deferred, $id, $msg) {
|
||||||
)->then(function () use ($id) {
|
|
||||||
$promise = new Promise;
|
|
||||||
$listener = function (Message $msg) use ($id, $promise, &$listener) {
|
|
||||||
if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) {
|
if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) {
|
||||||
// Received a response
|
// Received a response
|
||||||
$this->protocolReader->removeListener('message', $listener);
|
$this->protocolReader->removeListener('message', $listener);
|
||||||
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
||||||
$promise->fulfill($msg->body->result);
|
$deferred->resolve($msg->body->result);
|
||||||
} else {
|
} else {
|
||||||
$promise->reject($msg->body->error);
|
$deferred->fail($msg->body->error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
$this->protocolReader->on('message', $listener);
|
};
|
||||||
return $promise;
|
$this->protocolReader->addListener('message', $listener);
|
||||||
});
|
|
||||||
|
yield from $this->protocolWriter->write(
|
||||||
|
new Message(
|
||||||
|
new AdvancedJsonRpc\Request($id, $method, (object)$params)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return yield $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,11 +73,11 @@ class ClientHandler
|
||||||
*
|
*
|
||||||
* @param string $method The method to call
|
* @param string $method The method to call
|
||||||
* @param array|object $params The method parameters
|
* @param array|object $params The method parameters
|
||||||
* @return Promise <null> Will be resolved as soon as the notification has been sent
|
* @return \Generator <null> Will be resolved as soon as the notification has been sent
|
||||||
*/
|
*/
|
||||||
public function notify(string $method, $params): Promise
|
public function notify(string $method, $params): \Generator
|
||||||
{
|
{
|
||||||
return $this->protocolWriter->write(
|
return yield from $this->protocolWriter->write(
|
||||||
new Message(
|
new Message(
|
||||||
new AdvancedJsonRpc\Notification($method, (object)$params)
|
new AdvancedJsonRpc\Notification($method, (object)$params)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
use Amp\Loop;
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\FilesFinder\FileSystemFilesFinder;
|
||||||
use LanguageServer\Index\StubsIndex;
|
use LanguageServer\Index\StubsIndex;
|
||||||
|
use Microsoft\PhpParser;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
use Sabre\Uri;
|
use function League\Uri\parse;
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
use Microsoft\PhpParser;
|
|
||||||
|
|
||||||
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)) {
|
||||||
|
@ -23,8 +23,7 @@ class ComposerScripts
|
||||||
{
|
{
|
||||||
public static function parseStubs()
|
public static function parseStubs()
|
||||||
{
|
{
|
||||||
coroutine(function () {
|
Loop::run(function () {
|
||||||
|
|
||||||
$index = new StubsIndex;
|
$index = new StubsIndex;
|
||||||
|
|
||||||
$finder = new FileSystemFilesFinder;
|
$finder = new FileSystemFilesFinder;
|
||||||
|
@ -44,20 +43,19 @@ class ComposerScripts
|
||||||
throw new \Exception('jetbrains/phpstorm-stubs package not found');
|
throw new \Exception('jetbrains/phpstorm-stubs package not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
$uris = yield $finder->find("$stubsLocation/**/*.php");
|
$uris = yield from $finder->find("$stubsLocation/**/*.php");
|
||||||
|
|
||||||
foreach ($uris as $uri) {
|
foreach ($uris as $uri) {
|
||||||
echo "Parsing $uri\n";
|
echo "Parsing $uri\n";
|
||||||
$content = yield $contentRetriever->retrieve($uri);
|
$content = yield from $contentRetriever->retrieve($uri);
|
||||||
|
|
||||||
// Change URI to phpstubs://
|
// Change URI to phpstubs://
|
||||||
$parts = Uri\parse($uri);
|
$parts = parse($uri);
|
||||||
$parts['path'] = Path::makeRelative($parts['path'], $stubsLocation);
|
$parts['path'] = Path::makeRelative($parts['path'], $stubsLocation);
|
||||||
$parts['scheme'] = 'phpstubs';
|
$parts['scheme'] = 'phpstubs';
|
||||||
$uri = Uri\build($parts);
|
|
||||||
|
|
||||||
// Create a new document and add it to $index
|
// Create a new document and add it to $index
|
||||||
new PhpDocument($uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
new PhpDocument((string)$uri, $content, $index, $parser, $docBlockFactory, $definitionResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
$index->setComplete();
|
$index->setComplete();
|
||||||
|
@ -67,6 +65,6 @@ class ComposerScripts
|
||||||
$index->save();
|
$index->save();
|
||||||
|
|
||||||
echo "Finished\n";
|
echo "Finished\n";
|
||||||
})->wait();
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\ContentRetriever;
|
namespace LanguageServer\ContentRetriever;
|
||||||
|
|
||||||
|
@ -26,11 +26,10 @@ class ClientContentRetriever implements ContentRetriever
|
||||||
* @param string $uri The URI of the document
|
* @param string $uri The URI of the document
|
||||||
* @return Promise <string> Resolved with the content as a string
|
* @return Promise <string> Resolved with the content as a string
|
||||||
*/
|
*/
|
||||||
public function retrieve(string $uri): Promise
|
public function retrieve(string $uri): \Generator
|
||||||
{
|
{
|
||||||
return $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri))
|
/** @var TextDocumentItem $textDocument */
|
||||||
->then(function (TextDocumentItem $textDocument) {
|
$textDocument = yield from $this->client->textDocument->xcontent(new TextDocumentIdentifier($uri));
|
||||||
return $textDocument->text;
|
return $textDocument->text;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,5 @@ interface ContentRetriever
|
||||||
* @param string $uri The URI of the document
|
* @param string $uri The URI of the document
|
||||||
* @return Promise <string> Resolved with the content as a string
|
* @return Promise <string> Resolved with the content as a string
|
||||||
*/
|
*/
|
||||||
public function retrieve(string $uri): Promise;
|
public function retrieve(string $uri): \Generator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ class FileSystemContentRetriever implements ContentRetriever
|
||||||
* @param string $uri The URI of the document
|
* @param string $uri The URI of the document
|
||||||
* @return Promise <string> Resolved with the content as a string
|
* @return Promise <string> Resolved with the content as a string
|
||||||
*/
|
*/
|
||||||
public function retrieve(string $uri): Promise
|
public function retrieve(string $uri): \Generator
|
||||||
{
|
{
|
||||||
return Promise\resolve(file_get_contents(uriToPath($uri)));
|
return yield \Amp\File\get(uriToPath($uri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LanguageServer\Event;
|
||||||
|
|
||||||
|
use LanguageServer\Message;
|
||||||
|
use League\Event\Event;
|
||||||
|
|
||||||
|
class MessageEvent extends Event
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Message
|
||||||
|
*/
|
||||||
|
private $message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param Message $message
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, Message $message)
|
||||||
|
{
|
||||||
|
parent::__construct($name);
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): Message
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\FilesFinder;
|
namespace LanguageServer\FilesFinder;
|
||||||
|
|
||||||
use LanguageServer\LanguageClient;
|
use LanguageServer\LanguageClient;
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use Sabre\Uri;
|
|
||||||
use Webmozart\Glob\Glob;
|
use Webmozart\Glob\Glob;
|
||||||
|
use function League\Uri\parse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves file content from the client through a textDocument/xcontent request
|
* Retrieves file content from the client through a textDocument/xcontent request
|
||||||
|
@ -31,19 +30,18 @@ class ClientFilesFinder implements FilesFinder
|
||||||
* If the client does not support workspace/files, it falls back to searching the file system directly.
|
* If the client does not support workspace/files, it falls back to searching the file system directly.
|
||||||
*
|
*
|
||||||
* @param string $glob
|
* @param string $glob
|
||||||
* @return Promise <string[]> The URIs
|
* @return \Generator <string[]> The URIs
|
||||||
*/
|
*/
|
||||||
public function find(string $glob): Promise
|
public function find(string $glob): \Generator
|
||||||
{
|
{
|
||||||
return $this->client->workspace->xfiles()->then(function (array $textDocuments) use ($glob) {
|
$textDocuments = yield from $this->client->workspace->xfiles();
|
||||||
$uris = [];
|
$uris = [];
|
||||||
foreach ($textDocuments as $textDocument) {
|
foreach ($textDocuments as $textDocument) {
|
||||||
$path = Uri\parse($textDocument->uri)['path'];
|
$path = parse($textDocument->uri)['path'];
|
||||||
if (Glob::match($path, $glob)) {
|
if (Glob::match($path, $glob)) {
|
||||||
$uris[] = $textDocument->uri;
|
$uris[] = $textDocument->uri;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $uris;
|
}
|
||||||
});
|
return $uris;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\FilesFinder;
|
namespace LanguageServer\FilesFinder;
|
||||||
|
|
||||||
use Webmozart\Glob\Iterator\GlobIterator;
|
use Webmozart\Glob\Glob;
|
||||||
use Sabre\Event\Promise;
|
use function Amp\File\isdir;
|
||||||
use function Sabre\Event\coroutine;
|
use function LanguageServer\{pathToUri};
|
||||||
use function LanguageServer\{pathToUri, timeout};
|
|
||||||
|
|
||||||
class FileSystemFilesFinder implements FilesFinder
|
class FileSystemFilesFinder implements FilesFinder
|
||||||
{
|
{
|
||||||
|
@ -15,21 +14,24 @@ class FileSystemFilesFinder implements FilesFinder
|
||||||
* If the client does not support workspace/xfiles, it falls back to searching the file system directly.
|
* If the client does not support workspace/xfiles, it falls back to searching the file system directly.
|
||||||
*
|
*
|
||||||
* @param string $glob
|
* @param string $glob
|
||||||
* @return Promise <string[]>
|
* @return \Amp\Promise <string[]>
|
||||||
*/
|
*/
|
||||||
public function find(string $glob): Promise
|
public function find(string $glob): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($glob) {
|
$uris = [];
|
||||||
$uris = [];
|
$basePath = \Webmozart\Glob\Glob::getBasePath($glob);
|
||||||
foreach (new GlobIterator($glob) as $path) {
|
$pathList = [$basePath];
|
||||||
// Exclude any directories that also match the glob pattern
|
while ($pathList) {
|
||||||
if (!is_dir($path)) {
|
$path = array_pop($pathList);
|
||||||
$uris[] = pathToUri($path);
|
if (yield isdir($path)) {
|
||||||
|
$subFileList = yield \Amp\File\scandir($path);
|
||||||
|
foreach ($subFileList as $subFile) {
|
||||||
|
$pathList[] = $path . DIRECTORY_SEPARATOR . $subFile;
|
||||||
}
|
}
|
||||||
|
} elseif (Glob::match($path, $glob)) {
|
||||||
yield timeout();
|
$uris[] = pathToUri($path);
|
||||||
}
|
}
|
||||||
return $uris;
|
}
|
||||||
});
|
return $uris;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\FilesFinder;
|
namespace LanguageServer\FilesFinder;
|
||||||
|
|
||||||
|
@ -17,5 +17,5 @@ interface FilesFinder
|
||||||
* @param string $glob
|
* @param string $glob
|
||||||
* @return Promise <string[]>
|
* @return Promise <string[]>
|
||||||
*/
|
*/
|
||||||
public function find(string $glob): Promise;
|
public function find(string $glob): \Generator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,10 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Index;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
use LanguageServer\Definition;
|
use LanguageServer\Definition;
|
||||||
use Sabre\Event\EmitterTrait;
|
use League\Event\Emitter;
|
||||||
|
|
||||||
abstract class AbstractAggregateIndex implements ReadableIndex
|
abstract class AbstractAggregateIndex extends Emitter implements ReadableIndex
|
||||||
{
|
{
|
||||||
use EmitterTrait;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all indexes managed by the aggregate index
|
* Returns all indexes managed by the aggregate index
|
||||||
*
|
*
|
||||||
|
@ -29,17 +27,17 @@ abstract class AbstractAggregateIndex implements ReadableIndex
|
||||||
*/
|
*/
|
||||||
protected function registerIndex(ReadableIndex $index)
|
protected function registerIndex(ReadableIndex $index)
|
||||||
{
|
{
|
||||||
$index->on('complete', function () {
|
$index->addListener('complete', function () {
|
||||||
if ($this->isComplete()) {
|
if ($this->isComplete()) {
|
||||||
$this->emit('complete');
|
$this->emit('complete');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$index->on('static-complete', function () {
|
$index->addListener('static-complete', function () {
|
||||||
if ($this->isStaticComplete()) {
|
if ($this->isStaticComplete()) {
|
||||||
$this->emit('static-complete');
|
$this->emit('static-complete');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$index->on('definition-added', function () {
|
$index->addListener('definition-added', function () {
|
||||||
$this->emit('definition-added');
|
$this->emit('definition-added');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,13 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\Index;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
use League\Event\EmitterInterface;
|
||||||
|
use League\Event\EventInterface;
|
||||||
|
use League\Event\GeneratorInterface;
|
||||||
|
use League\Event\ListenerAcceptorInterface;
|
||||||
|
use League\Event\ListenerInterface;
|
||||||
|
use League\Event\ListenerProviderInterface;
|
||||||
|
|
||||||
class DependenciesIndex extends AbstractAggregateIndex
|
class DependenciesIndex extends AbstractAggregateIndex
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Index;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
use Ds\Set;
|
||||||
use LanguageServer\Definition;
|
use LanguageServer\Definition;
|
||||||
use Sabre\Event\EmitterTrait;
|
use League\Event\Emitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the index of a project or dependency
|
* Represents the index of a project or dependency
|
||||||
* Serializable for caching
|
* Serializable for caching
|
||||||
*/
|
*/
|
||||||
class Index implements ReadableIndex, \Serializable
|
class Index extends Emitter implements ReadableIndex, \Serializable
|
||||||
{
|
{
|
||||||
use EmitterTrait;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An associative array that maps splitted fully qualified symbol names
|
* An associative array that maps splitted fully qualified symbol names
|
||||||
|
@ -36,7 +36,7 @@ class Index implements ReadableIndex, \Serializable
|
||||||
* An associative array that maps fully qualified symbol names
|
* An associative array that maps fully qualified symbol names
|
||||||
* to arrays of document URIs that reference the symbol
|
* to arrays of document URIs that reference the symbol
|
||||||
*
|
*
|
||||||
* @var string[][]
|
* @var Set[]
|
||||||
*/
|
*/
|
||||||
private $references = [];
|
private $references = [];
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ class Index implements ReadableIndex, \Serializable
|
||||||
$this->setStaticComplete();
|
$this->setStaticComplete();
|
||||||
}
|
}
|
||||||
$this->complete = true;
|
$this->complete = true;
|
||||||
$this->emit('complete');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +71,6 @@ class Index implements ReadableIndex, \Serializable
|
||||||
public function setStaticComplete()
|
public function setStaticComplete()
|
||||||
{
|
{
|
||||||
$this->staticComplete = true;
|
$this->staticComplete = true;
|
||||||
$this->emit('static-complete');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,9 +129,9 @@ class Index implements ReadableIndex, \Serializable
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($item instanceof Definition) {
|
if ($item instanceof Definition) {
|
||||||
yield $fqn.$name => $item;
|
yield $fqn . $name => $item;
|
||||||
} elseif (is_array($item) && isset($item[''])) {
|
} elseif (is_array($item) && isset($item[''])) {
|
||||||
yield $fqn.$name => $item[''];
|
yield $fqn . $name => $item[''];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,8 +171,6 @@ class Index implements ReadableIndex, \Serializable
|
||||||
{
|
{
|
||||||
$parts = $this->splitFqn($fqn);
|
$parts = $this->splitFqn($fqn);
|
||||||
$this->indexDefinition(0, $parts, $this->definitions, $definition);
|
$this->indexDefinition(0, $parts, $this->definitions, $definition);
|
||||||
|
|
||||||
$this->emit('definition-added');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,8 +196,10 @@ class Index implements ReadableIndex, \Serializable
|
||||||
*/
|
*/
|
||||||
public function getReferenceUris(string $fqn): \Generator
|
public function getReferenceUris(string $fqn): \Generator
|
||||||
{
|
{
|
||||||
foreach ($this->references[$fqn] ?? [] as $uri) {
|
if (isset($this->references[$fqn])) {
|
||||||
yield $uri;
|
foreach ($this->references[$fqn] as $uri) {
|
||||||
|
yield $uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +207,7 @@ class Index implements ReadableIndex, \Serializable
|
||||||
* For test use.
|
* For test use.
|
||||||
* Returns all references, keyed by fqn.
|
* Returns all references, keyed by fqn.
|
||||||
*
|
*
|
||||||
* @return string[][]
|
* @return Set[]
|
||||||
*/
|
*/
|
||||||
public function getReferences(): array
|
public function getReferences(): array
|
||||||
{
|
{
|
||||||
|
@ -225,12 +223,9 @@ class Index implements ReadableIndex, \Serializable
|
||||||
public function addReferenceUri(string $fqn, string $uri)
|
public function addReferenceUri(string $fqn, string $uri)
|
||||||
{
|
{
|
||||||
if (!isset($this->references[$fqn])) {
|
if (!isset($this->references[$fqn])) {
|
||||||
$this->references[$fqn] = [];
|
$this->references[$fqn] = new Set();
|
||||||
}
|
|
||||||
// TODO: use DS\Set instead of searching array
|
|
||||||
if (array_search($uri, $this->references[$fqn], true) === false) {
|
|
||||||
$this->references[$fqn][] = $uri;
|
|
||||||
}
|
}
|
||||||
|
$this->references[$fqn]->add($uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -245,11 +240,7 @@ class Index implements ReadableIndex, \Serializable
|
||||||
if (!isset($this->references[$fqn])) {
|
if (!isset($this->references[$fqn])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$index = array_search($fqn, $this->references[$fqn], true);
|
$this->references[$fqn]->remove($uri);
|
||||||
if ($index === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
array_splice($this->references[$fqn], $index, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -299,9 +290,9 @@ class Index implements ReadableIndex, \Serializable
|
||||||
{
|
{
|
||||||
foreach ($storage as $key => $value) {
|
foreach ($storage as $key => $value) {
|
||||||
if (!is_array($value)) {
|
if (!is_array($value)) {
|
||||||
yield $prefix.$key => $value;
|
yield $prefix . $key => $value;
|
||||||
} else {
|
} else {
|
||||||
yield from $this->yieldDefinitionsRecursively($value, $prefix.$key);
|
yield from $this->yieldDefinitionsRecursively($value, $prefix . $key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,8 +356,8 @@ class Index implements ReadableIndex, \Serializable
|
||||||
* It can be an index node or a Definition if the $parts are precise
|
* It can be an index node or a Definition if the $parts are precise
|
||||||
* enough. Returns null when nothing is found.
|
* enough. Returns null when nothing is found.
|
||||||
*
|
*
|
||||||
* @param string[] $path The splitted FQN
|
* @param string[] $path The splitted FQN
|
||||||
* @param array|Definition &$storage The current level to look for $path.
|
* @param array|Definition &$storage The current level to look for $path.
|
||||||
* @return array|Definition|null
|
* @return array|Definition|null
|
||||||
*/
|
*/
|
||||||
private function getIndexValue(array $path, &$storage)
|
private function getIndexValue(array $path, &$storage)
|
||||||
|
@ -389,10 +380,10 @@ class Index implements ReadableIndex, \Serializable
|
||||||
* Recursive function that stores the given Definition in the given $storage array represented
|
* Recursive function that stores the given Definition in the given $storage array represented
|
||||||
* as a tree matching the given $parts.
|
* as a tree matching the given $parts.
|
||||||
*
|
*
|
||||||
* @param int $level The current level of FQN part
|
* @param int $level The current level of FQN part
|
||||||
* @param string[] $parts The splitted FQN
|
* @param string[] $parts The splitted FQN
|
||||||
* @param array &$storage The array in which to store the $definition
|
* @param array &$storage The array in which to store the $definition
|
||||||
* @param Definition $definition The Definition to store
|
* @param Definition $definition The Definition to store
|
||||||
*/
|
*/
|
||||||
private function indexDefinition(int $level, array $parts, array &$storage, Definition $definition)
|
private function indexDefinition(int $level, array $parts, array &$storage, Definition $definition)
|
||||||
{
|
{
|
||||||
|
@ -416,10 +407,10 @@ class Index implements ReadableIndex, \Serializable
|
||||||
* $storage array. The function also looks up recursively to remove the parents of the
|
* $storage array. The function also looks up recursively to remove the parents of the
|
||||||
* definition which no longer has children to avoid to let empty arrays in the index.
|
* definition which no longer has children to avoid to let empty arrays in the index.
|
||||||
*
|
*
|
||||||
* @param int $level The current level of FQN part
|
* @param int $level The current level of FQN part
|
||||||
* @param string[] $parts The splitted FQN
|
* @param string[] $parts The splitted FQN
|
||||||
* @param array &$storage The current array in which to remove data
|
* @param array &$storage The current array in which to remove data
|
||||||
* @param array &$rootStorage The root storage array
|
* @param array &$rootStorage The root storage array
|
||||||
*/
|
*/
|
||||||
private function removeIndexedDefinition(int $level, array $parts, array &$storage, array &$rootStorage)
|
private function removeIndexedDefinition(int $level, array $parts, array &$storage, array &$rootStorage)
|
||||||
{
|
{
|
||||||
|
@ -429,7 +420,7 @@ class Index implements ReadableIndex, \Serializable
|
||||||
if (isset($storage[$part])) {
|
if (isset($storage[$part])) {
|
||||||
unset($storage[$part]);
|
unset($storage[$part]);
|
||||||
|
|
||||||
if (0 === count($storage)) {
|
if (0 === count($storage) && $level != 0) {
|
||||||
// parse again the definition tree to remove the parent
|
// parse again the definition tree to remove the parent
|
||||||
// when it has no more children
|
// when it has no more children
|
||||||
$this->removeIndexedDefinition(0, array_slice($parts, 0, $level), $rootStorage, $rootStorage);
|
$this->removeIndexedDefinition(0, array_slice($parts, 0, $level), $rootStorage, $rootStorage);
|
||||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types = 1);
|
||||||
namespace LanguageServer\Index;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
use LanguageServer\Definition;
|
use LanguageServer\Definition;
|
||||||
use Sabre\Event\EmitterInterface;
|
use League\Event\EmitterInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ReadableIndex interface provides methods to lookup definitions and references
|
* The ReadableIndex interface provides methods to lookup definitions and references
|
||||||
|
|
225
src/Indexer.php
225
src/Indexer.php
|
@ -1,15 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use Amp\Delayed;
|
||||||
use LanguageServer\Cache\Cache;
|
use LanguageServer\Cache\Cache;
|
||||||
use LanguageServer\FilesFinder\FilesFinder;
|
use LanguageServer\FilesFinder\FilesFinder;
|
||||||
use LanguageServer\Index\{DependenciesIndex, Index};
|
use LanguageServer\Index\{DependenciesIndex, Index};
|
||||||
use LanguageServerProtocol\MessageType;
|
use LanguageServerProtocol\MessageType;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
|
|
||||||
class Indexer
|
class Indexer
|
||||||
{
|
{
|
||||||
|
@ -64,14 +64,14 @@ class Indexer
|
||||||
private $composerJson;
|
private $composerJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FilesFinder $filesFinder
|
* @param FilesFinder $filesFinder
|
||||||
* @param string $rootPath
|
* @param string $rootPath
|
||||||
* @param LanguageClient $client
|
* @param LanguageClient $client
|
||||||
* @param Cache $cache
|
* @param Cache $cache
|
||||||
* @param DependenciesIndex $dependenciesIndex
|
* @param DependenciesIndex $dependenciesIndex
|
||||||
* @param Index $sourceIndex
|
* @param Index $sourceIndex
|
||||||
* @param PhpDocumentLoader $documentLoader
|
* @param PhpDocumentLoader $documentLoader
|
||||||
* @param \stdClass|null $composerLock
|
* @param \stdClass|null $composerLock
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
FilesFinder $filesFinder,
|
FilesFinder $filesFinder,
|
||||||
|
@ -100,132 +100,129 @@ class Indexer
|
||||||
*
|
*
|
||||||
* @return Promise <void>
|
* @return Promise <void>
|
||||||
*/
|
*/
|
||||||
public function index(): Promise
|
public function index(): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () {
|
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
||||||
|
$uris = yield from $this->filesFinder->find($pattern);
|
||||||
|
|
||||||
$pattern = Path::makeAbsolute('**/*.php', $this->rootPath);
|
$count = count($uris);
|
||||||
$uris = yield $this->filesFinder->find($pattern);
|
$startTime = microtime(true);
|
||||||
|
yield from $this->client->window->logMessage(MessageType::INFO, "$count files total");
|
||||||
|
|
||||||
$count = count($uris);
|
/** @var string[] */
|
||||||
$startTime = microtime(true);
|
$source = [];
|
||||||
$this->client->window->logMessage(MessageType::INFO, "$count files total");
|
/** @var string[][] */
|
||||||
|
$deps = [];
|
||||||
|
|
||||||
/** @var string[] */
|
foreach ($uris as $uri) {
|
||||||
$source = [];
|
$packageName = getPackageName($uri, $this->composerJson);
|
||||||
/** @var string[][] */
|
if ($this->composerLock !== null && $packageName) {
|
||||||
$deps = [];
|
// Dependency file
|
||||||
|
if (!isset($deps[$packageName])) {
|
||||||
|
$deps[$packageName] = [];
|
||||||
|
}
|
||||||
|
$deps[$packageName][] = $uri;
|
||||||
|
} else {
|
||||||
|
// Source file
|
||||||
|
$source[] = $uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($uris as $uri) {
|
// Index source
|
||||||
$packageName = getPackageName($uri, $this->composerJson);
|
// Definitions and static references
|
||||||
if ($this->composerLock !== null && $packageName) {
|
yield from $this->client->window->logMessage(MessageType::INFO, 'Indexing project for definitions and static references');
|
||||||
// Dependency file
|
yield from $this->indexFiles($source);
|
||||||
if (!isset($deps[$packageName])) {
|
$this->sourceIndex->setStaticComplete();
|
||||||
$deps[$packageName] = [];
|
// Dynamic references
|
||||||
}
|
yield from $this->client->window->logMessage(MessageType::INFO, 'Indexing project for dynamic references');
|
||||||
$deps[$packageName][] = $uri;
|
yield from $this->indexFiles($source);
|
||||||
} else {
|
$this->sourceIndex->setComplete();
|
||||||
// Source file
|
|
||||||
$source[] = $uri;
|
// Index dependencies
|
||||||
|
yield from $this->client->window->logMessage(MessageType::INFO, count($deps) . ' Packages');
|
||||||
|
foreach ($deps as $packageName => $files) {
|
||||||
|
// Find version of package and check cache
|
||||||
|
$packageKey = null;
|
||||||
|
$cacheKey = null;
|
||||||
|
$index = null;
|
||||||
|
foreach (array_merge($this->composerLock->packages, (array)$this->composerLock->{'packages-dev'}) as $package) {
|
||||||
|
// Check if package name matches and version is absolute
|
||||||
|
// Dynamic constraints are not cached, because they can change every time
|
||||||
|
$packageVersion = ltrim($package->version, 'v');
|
||||||
|
if ($package->name === $packageName && strpos($packageVersion, 'dev') === false) {
|
||||||
|
$packageKey = $packageName . ':' . $packageVersion;
|
||||||
|
$cacheKey = self::CACHE_VERSION . ':' . $packageKey;
|
||||||
|
// Check cache
|
||||||
|
$index = yield from $this->cache->get($cacheKey);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$index = null;
|
||||||
|
if ($index !== null) {
|
||||||
|
// Cache hit
|
||||||
|
$this->dependenciesIndex->setDependencyIndex($packageName, $index);
|
||||||
|
yield from $this->client->window->logMessage(MessageType::INFO, "Restored $packageKey from cache");
|
||||||
|
} else {
|
||||||
|
// Cache miss
|
||||||
|
$index = $this->dependenciesIndex->getDependencyIndex($packageName);
|
||||||
|
|
||||||
// Index source
|
// Index definitions and static references
|
||||||
// Definitions and static references
|
yield from $this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for definitions and static references');
|
||||||
$this->client->window->logMessage(MessageType::INFO, 'Indexing project for definitions and static references');
|
yield from $this->indexFiles($files);
|
||||||
yield $this->indexFiles($source);
|
$index->setStaticComplete();
|
||||||
$this->sourceIndex->setStaticComplete();
|
|
||||||
// Dynamic references
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, 'Indexing project for dynamic references');
|
|
||||||
yield $this->indexFiles($source);
|
|
||||||
$this->sourceIndex->setComplete();
|
|
||||||
|
|
||||||
// Index dependencies
|
// Index dynamic references
|
||||||
$this->client->window->logMessage(MessageType::INFO, count($deps) . ' Packages');
|
yield from $this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for dynamic references');
|
||||||
foreach ($deps as $packageName => $files) {
|
yield from $this->indexFiles($files);
|
||||||
// Find version of package and check cache
|
$index->setComplete();
|
||||||
$packageKey = null;
|
|
||||||
$cacheKey = null;
|
// If we know the version (cache key), save index for the dependency in the cache
|
||||||
$index = null;
|
if ($cacheKey !== null) {
|
||||||
foreach (array_merge($this->composerLock->packages, (array)$this->composerLock->{'packages-dev'}) as $package) {
|
yield from $this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
|
||||||
// Check if package name matches and version is absolute
|
yield from $this->cache->set($cacheKey, $index);
|
||||||
// Dynamic constraints are not cached, because they can change every time
|
|
||||||
$packageVersion = ltrim($package->version, 'v');
|
|
||||||
if ($package->name === $packageName && strpos($packageVersion, 'dev') === false) {
|
|
||||||
$packageKey = $packageName . ':' . $packageVersion;
|
|
||||||
$cacheKey = self::CACHE_VERSION . ':' . $packageKey;
|
|
||||||
// Check cache
|
|
||||||
$index = yield $this->cache->get($cacheKey);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($index !== null) {
|
|
||||||
// Cache hit
|
|
||||||
$this->dependenciesIndex->setDependencyIndex($packageName, $index);
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Restored $packageKey from cache");
|
|
||||||
} else {
|
} else {
|
||||||
// Cache miss
|
yield from $this->client->window->logMessage(MessageType::WARNING, "Could not compute cache key for $packageName");
|
||||||
$index = $this->dependenciesIndex->getDependencyIndex($packageName);
|
|
||||||
|
|
||||||
// Index definitions and static references
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for definitions and static references');
|
|
||||||
yield $this->indexFiles($files);
|
|
||||||
$index->setStaticComplete();
|
|
||||||
|
|
||||||
// Index dynamic references
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, 'Indexing ' . ($packageKey ?? $packageName) . ' for dynamic references');
|
|
||||||
yield $this->indexFiles($files);
|
|
||||||
$index->setComplete();
|
|
||||||
|
|
||||||
// If we know the version (cache key), save index for the dependency in the cache
|
|
||||||
if ($cacheKey !== null) {
|
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
|
|
||||||
$this->cache->set($cacheKey, $index);
|
|
||||||
} else {
|
|
||||||
$this->client->window->logMessage(MessageType::WARNING, "Could not compute cache key for $packageName");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
echo PHP_EOL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$duration = (int)(microtime(true) - $startTime);
|
$duration = (int)(microtime(true) - $startTime);
|
||||||
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
|
||||||
$this->client->window->logMessage(
|
yield from $this->client->window->logMessage(
|
||||||
MessageType::INFO,
|
MessageType::INFO,
|
||||||
"All $count PHP files parsed in $duration seconds. $mem MiB allocated."
|
"All $count PHP files parsed in $duration seconds. $mem MiB allocated."
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $files
|
* @param array $files
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
private function indexFiles(array $files): Promise
|
private function indexFiles(array $files): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($files) {
|
foreach ($files as $i => $uri) {
|
||||||
foreach ($files as $i => $uri) {
|
// Skip open documents
|
||||||
// Skip open documents
|
if ($this->documentLoader->isOpen($uri)) {
|
||||||
if ($this->documentLoader->isOpen($uri)) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give LS to the chance to handle requests while indexing
|
|
||||||
yield timeout();
|
|
||||||
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
|
||||||
try {
|
|
||||||
$document = yield $this->documentLoader->load($uri);
|
|
||||||
if (!isVendored($document, $this->composerJson)) {
|
|
||||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
|
||||||
}
|
|
||||||
} catch (ContentTooLargeException $e) {
|
|
||||||
$this->client->window->logMessage(
|
|
||||||
MessageType::INFO,
|
|
||||||
"Ignoring file {$uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
|
||||||
);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->client->window->logMessage(MessageType::ERROR, "Error parsing $uri: " . (string)$e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Give LS to the chance to handle requests while indexing
|
||||||
|
yield new Delayed(0);
|
||||||
|
yield from $this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
||||||
|
try {
|
||||||
|
$document = yield from $this->documentLoader->load($uri);
|
||||||
|
if (!isVendored($document, $this->composerJson)) {
|
||||||
|
yield from $this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
||||||
|
}
|
||||||
|
} catch (ContentTooLargeException $e) {
|
||||||
|
yield from $this->client->window->logMessage(
|
||||||
|
MessageType::INFO,
|
||||||
|
"Ignoring file {$uri} because it exceeds size limit of {$e->limit} bytes ({$e->size})"
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
yield from $this->client->window->logMessage(MessageType::ERROR, "Error parsing $uri: " . (string)$e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServerProtocol\{
|
|
||||||
ServerCapabilities,
|
|
||||||
ClientCapabilities,
|
|
||||||
TextDocumentSyncKind,
|
|
||||||
InitializeResult,
|
|
||||||
CompletionOptions,
|
|
||||||
SignatureHelpOptions
|
|
||||||
};
|
|
||||||
use LanguageServer\Message;
|
|
||||||
use LanguageServer\FilesFinder\{FilesFinder, ClientFilesFinder, FileSystemFilesFinder};
|
|
||||||
use LanguageServer\ContentRetriever\{ContentRetriever, ClientContentRetriever, FileSystemContentRetriever};
|
|
||||||
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
|
||||||
use LanguageServer\Cache\{FileSystemCache, ClientCache};
|
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Sabre\Event\Promise;
|
use Amp\Deferred;
|
||||||
use function Sabre\Event\coroutine;
|
use Amp\Delayed;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Promise;
|
||||||
|
use LanguageServer\Cache\{ClientCache, FileSystemCache};
|
||||||
|
use LanguageServer\ContentRetriever\{ClientContentRetriever, ContentRetriever, FileSystemContentRetriever};
|
||||||
|
use LanguageServer\Event\MessageEvent;
|
||||||
|
use LanguageServer\FilesFinder\{ClientFilesFinder, FilesFinder, FileSystemFilesFinder};
|
||||||
|
use LanguageServer\Index\{DependenciesIndex, GlobalIndex, Index, ProjectIndex, StubsIndex};
|
||||||
|
use LanguageServerProtocol\{ClientCapabilities,
|
||||||
|
CompletionOptions,
|
||||||
|
InitializeResult,
|
||||||
|
ServerCapabilities,
|
||||||
|
SignatureHelpOptions,
|
||||||
|
TextDocumentSyncKind};
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
|
|
||||||
|
@ -38,11 +38,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
*/
|
*/
|
||||||
public $workspace;
|
public $workspace;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Server\Window
|
|
||||||
*/
|
|
||||||
public $window;
|
|
||||||
|
|
||||||
public $telemetry;
|
public $telemetry;
|
||||||
public $completionItem;
|
public $completionItem;
|
||||||
public $codeLens;
|
public $codeLens;
|
||||||
|
@ -107,19 +102,29 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
protected $definitionResolver;
|
protected $definitionResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ProtocolReader $reader
|
* @var Deferred
|
||||||
|
*/
|
||||||
|
private $shutdownDeferred;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ProtocolReader $reader
|
||||||
* @param ProtocolWriter $writer
|
* @param ProtocolWriter $writer
|
||||||
*/
|
*/
|
||||||
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
|
public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
|
||||||
{
|
{
|
||||||
parent::__construct($this, '/');
|
parent::__construct($this, '/');
|
||||||
|
|
||||||
|
$this->shutdownDeferred = new Deferred();
|
||||||
|
|
||||||
$this->protocolReader = $reader;
|
$this->protocolReader = $reader;
|
||||||
$this->protocolReader->on('close', function () {
|
$this->protocolReader->addListener('close', function () {
|
||||||
$this->shutdown();
|
$this->shutdown();
|
||||||
$this->exit();
|
|
||||||
});
|
});
|
||||||
$this->protocolReader->on('message', function (Message $msg) {
|
$this->protocolWriter = $writer;
|
||||||
coroutine(function () use ($msg) {
|
$this->client = new LanguageClient($reader, $writer);
|
||||||
|
$this->protocolReader->addListener('message', function (MessageEvent $messageEvent) use ($reader, $writer) {
|
||||||
|
$msg = $messageEvent->getMessage();
|
||||||
|
Loop::defer(function () use ($msg) {
|
||||||
// Ignore responses, this is the handler for requests and notifications
|
// Ignore responses, this is the handler for requests and notifications
|
||||||
if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
|
if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
|
||||||
return;
|
return;
|
||||||
|
@ -149,12 +154,15 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
} else {
|
} else {
|
||||||
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
|
$responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
|
||||||
}
|
}
|
||||||
$this->protocolWriter->write(new Message($responseBody));
|
yield from $this->protocolWriter->write(new Message($responseBody));
|
||||||
}
|
}
|
||||||
})->otherwise('\\LanguageServer\\crash');
|
});
|
||||||
});
|
});
|
||||||
$this->protocolWriter = $writer;
|
}
|
||||||
$this->client = new LanguageClient($reader, $writer);
|
|
||||||
|
public function getshutdownDeferred(): Promise
|
||||||
|
{
|
||||||
|
return $this->shutdownDeferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,15 +171,13 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
* @param ClientCapabilities $capabilities The capabilities provided by the client (editor)
|
* @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 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.
|
* @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.
|
||||||
|
* @param string|null $rootUri
|
||||||
* @return Promise <InitializeResult>
|
* @return Promise <InitializeResult>
|
||||||
*/
|
*/
|
||||||
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null, string $rootUri = null): Promise
|
public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null, string $rootUri = null): Promise
|
||||||
{
|
{
|
||||||
if ($rootPath === null && $rootUri !== null) {
|
$deferred = new Deferred();
|
||||||
$rootPath = uriToPath($rootUri);
|
Loop::defer(function () use ($deferred, $capabilities, $rootPath, $processId, $rootUri) {
|
||||||
}
|
|
||||||
return coroutine(function () use ($capabilities, $rootPath, $processId) {
|
|
||||||
|
|
||||||
if ($capabilities->xfilesProvider) {
|
if ($capabilities->xfilesProvider) {
|
||||||
$this->filesFinder = new ClientFilesFinder($this->client);
|
$this->filesFinder = new ClientFilesFinder($this->client);
|
||||||
} else {
|
} else {
|
||||||
|
@ -200,25 +206,25 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($rootPath !== null) {
|
if ($rootPath !== null) {
|
||||||
yield $this->beforeIndex($rootPath);
|
yield from $this->beforeIndex($rootPath);
|
||||||
|
|
||||||
// Find composer.json
|
// Find composer.json
|
||||||
if ($this->composerJson === null) {
|
if ($this->composerJson === null) {
|
||||||
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
$composerJsonFiles = yield from $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
||||||
sortUrisLevelOrder($composerJsonFiles);
|
sortUrisLevelOrder($composerJsonFiles);
|
||||||
|
|
||||||
if (!empty($composerJsonFiles)) {
|
if (!empty($composerJsonFiles)) {
|
||||||
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
$this->composerJson = json_decode(yield from $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find composer.lock
|
// Find composer.lock
|
||||||
if ($this->composerLock === null) {
|
if ($this->composerLock === null) {
|
||||||
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
$composerLockFiles = yield from $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
||||||
sortUrisLevelOrder($composerLockFiles);
|
sortUrisLevelOrder($composerLockFiles);
|
||||||
|
|
||||||
if (!empty($composerLockFiles)) {
|
if (!empty($composerLockFiles)) {
|
||||||
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
$this->composerLock = json_decode(yield from $this->contentRetriever->retrieve($composerLockFiles[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +242,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$this->composerLock,
|
$this->composerLock,
|
||||||
$this->composerJson
|
$this->composerJson
|
||||||
);
|
);
|
||||||
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
Loop::defer(function () use ($indexer) {
|
||||||
|
yield from $indexer->index();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -288,8 +296,9 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$serverCapabilities->xdefinitionProvider = true;
|
$serverCapabilities->xdefinitionProvider = true;
|
||||||
$serverCapabilities->xdependenciesProvider = true;
|
$serverCapabilities->xdependenciesProvider = true;
|
||||||
|
|
||||||
return new InitializeResult($serverCapabilities);
|
$deferred->resolve(new InitializeResult($serverCapabilities));
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,29 +306,23 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
* (otherwise the response might not be delivered correctly to the client). There is a separate exit notification that
|
* (otherwise the response might not be delivered correctly to the client). There is a separate exit notification that
|
||||||
* asks the server to exit.
|
* asks the server to exit.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
public function shutdown()
|
public function shutdown()
|
||||||
{
|
{
|
||||||
unset($this->project);
|
unset($this->project);
|
||||||
}
|
$this->shutdownDeferred->resolve();
|
||||||
|
yield new Delayed(0);
|
||||||
/**
|
|
||||||
* A notification to ask the server to exit its process.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function exit()
|
|
||||||
{
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before indexing, can return a Promise
|
* Called before indexing, can return a Promise
|
||||||
*
|
*
|
||||||
* @param string $rootPath
|
* @param string $rootPath
|
||||||
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
protected function beforeIndex(string $rootPath)
|
protected function beforeIndex(string $rootPath)
|
||||||
{
|
{
|
||||||
|
yield new Delayed(0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use AdvancedJsonRpc\Message as MessageBody;
|
use AdvancedJsonRpc\Message as MessageBody;
|
||||||
use LanguageServer\Message;
|
|
||||||
|
|
||||||
class Message
|
class Message
|
||||||
{
|
{
|
||||||
|
@ -27,7 +26,7 @@ class Message
|
||||||
public static function parse(string $msg): Message
|
public static function parse(string $msg): Message
|
||||||
{
|
{
|
||||||
$obj = new self;
|
$obj = new self;
|
||||||
$parts = explode("\r\n", $msg);
|
$parts = explode("\r\n\r\n", $msg, 2);
|
||||||
$obj->body = MessageBody::parse(array_pop($parts));
|
$obj->body = MessageBody::parse(array_pop($parts));
|
||||||
foreach ($parts as $line) {
|
foreach ($parts as $line) {
|
||||||
if ($line) {
|
if ($line) {
|
||||||
|
@ -55,11 +54,11 @@ class Message
|
||||||
{
|
{
|
||||||
$body = (string)$this->body;
|
$body = (string)$this->body;
|
||||||
$contentLength = strlen($body);
|
$contentLength = strlen($body);
|
||||||
$this->headers['Content-Length'] = $contentLength;
|
$this->headers['Content-Length'] = $contentLength + 6;
|
||||||
$headers = '';
|
$headers = '';
|
||||||
foreach ($this->headers as $name => $value) {
|
foreach ($this->headers as $name => $value) {
|
||||||
$headers .= "$name: $value\r\n";
|
$headers .= "$name: $value\r\n";
|
||||||
}
|
}
|
||||||
return $headers . "\r\n" . $body;
|
return $headers . "\r\n" . $body . "\r\n\r\n\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\ContentRetriever\ContentRetriever;
|
use LanguageServer\ContentRetriever\ContentRetriever;
|
||||||
use LanguageServer\Index\ProjectIndex;
|
use LanguageServer\Index\ProjectIndex;
|
||||||
use phpDocumentor\Reflection\DocBlockFactory;
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
use Microsoft\PhpParser;
|
use Microsoft\PhpParser;
|
||||||
|
use Microsoft\PhpParser\Parser;
|
||||||
|
use phpDocumentor\Reflection\DocBlockFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes care of loading documents and managing "open" documents
|
* Takes care of loading documents and managing "open" documents
|
||||||
|
@ -18,7 +17,7 @@ class PhpDocumentLoader
|
||||||
/**
|
/**
|
||||||
* A map from URI => PhpDocument of open documents that should be kept in memory
|
* A map from URI => PhpDocument of open documents that should be kept in memory
|
||||||
*
|
*
|
||||||
* @var PhpDocument
|
* @var PhpDocument[]
|
||||||
*/
|
*/
|
||||||
private $documents = [];
|
private $documents = [];
|
||||||
|
|
||||||
|
@ -37,11 +36,6 @@ class PhpDocumentLoader
|
||||||
*/
|
*/
|
||||||
private $parser;
|
private $parser;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PhpParser\Parser
|
|
||||||
*/
|
|
||||||
private $tolerantParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var DocBlockFactory
|
* @var DocBlockFactory
|
||||||
*/
|
*/
|
||||||
|
@ -87,11 +81,16 @@ class PhpDocumentLoader
|
||||||
* If the document is not open, loads it.
|
* If the document is not open, loads it.
|
||||||
*
|
*
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
* @return Promise <PhpDocument>
|
* @return \Generator <PhpDocument>
|
||||||
|
* @throws ContentTooLargeException
|
||||||
*/
|
*/
|
||||||
public function getOrLoad(string $uri): Promise
|
public function getOrLoad(string $uri): \Generator
|
||||||
{
|
{
|
||||||
return isset($this->documents[$uri]) ? Promise\resolve($this->documents[$uri]) : $this->load($uri);
|
if (isset($this->documents[$uri])) {
|
||||||
|
return $this->documents[$uri];
|
||||||
|
} else {
|
||||||
|
return yield from $this->load($uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,27 +99,25 @@ class PhpDocumentLoader
|
||||||
* The document is NOT added to the list of open documents, but definitions are registered.
|
* The document is NOT added to the list of open documents, but definitions are registered.
|
||||||
*
|
*
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
* @return Promise <PhpDocument>
|
* @return \Generator <PhpDocument>
|
||||||
|
* @throws ContentTooLargeException
|
||||||
*/
|
*/
|
||||||
public function load(string $uri): Promise
|
public function load(string $uri): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($uri) {
|
$limit = 150000;
|
||||||
|
$content = yield from $this->contentRetriever->retrieve($uri);
|
||||||
|
$size = strlen($content);
|
||||||
|
if ($size > $limit) {
|
||||||
|
throw new ContentTooLargeException($uri, $size, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
$limit = 150000;
|
if (isset($this->documents[$uri])) {
|
||||||
$content = yield $this->contentRetriever->retrieve($uri);
|
$document = $this->documents[$uri];
|
||||||
$size = strlen($content);
|
$document->updateContent($content);
|
||||||
if ($size > $limit) {
|
} else {
|
||||||
throw new ContentTooLargeException($uri, $size, $limit);
|
$document = $this->create($uri, $content);
|
||||||
}
|
}
|
||||||
|
return $document;
|
||||||
if (isset($this->documents[$uri])) {
|
|
||||||
$document = $this->documents[$uri];
|
|
||||||
$document->updateContent($content);
|
|
||||||
} else {
|
|
||||||
$document = $this->create($uri, $content);
|
|
||||||
}
|
|
||||||
return $document;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,7 +144,7 @@ class PhpDocumentLoader
|
||||||
*
|
*
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
* @param string $content
|
* @param string $content
|
||||||
* @return void
|
* @return PhpDocument
|
||||||
*/
|
*/
|
||||||
public function open(string $uri, string $content)
|
public function open(string $uri, string $content)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use Sabre\Event\EmitterInterface;
|
use League\Event\EmitterInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must emit a "message" event with a Protocol\Message object as parameter
|
* Must emit a "message" event with a Protocol\Message object as parameter
|
||||||
|
|
|
@ -1,65 +1,54 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use LanguageServer\Message;
|
|
||||||
use AdvancedJsonRpc\Message as MessageBody;
|
use AdvancedJsonRpc\Message as MessageBody;
|
||||||
use Sabre\Event\{Loop, Emitter};
|
use Amp\ByteStream\InputStream;
|
||||||
|
use Amp\Loop;
|
||||||
|
use LanguageServer\Event\MessageEvent;
|
||||||
|
use League\Event\Emitter;
|
||||||
|
|
||||||
class ProtocolStreamReader extends Emitter implements ProtocolReader
|
class ProtocolStreamReader extends Emitter implements ProtocolReader
|
||||||
{
|
{
|
||||||
const PARSE_HEADERS = 1;
|
|
||||||
const PARSE_BODY = 2;
|
|
||||||
|
|
||||||
private $input;
|
private $input;
|
||||||
private $parsingMode = self::PARSE_HEADERS;
|
|
||||||
private $buffer = '';
|
|
||||||
private $headers = [];
|
|
||||||
private $contentLength;
|
|
||||||
|
|
||||||
/**
|
public function __construct(InputStream $input)
|
||||||
* @param resource $input
|
|
||||||
*/
|
|
||||||
public function __construct($input)
|
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
|
Loop::defer(function () use (&$input) {
|
||||||
$this->on('close', function () {
|
$buffer = '';
|
||||||
Loop\removeReadStream($this->input);
|
while (true) {
|
||||||
});
|
$headers = [];
|
||||||
|
while (true) {
|
||||||
Loop\addReadStream($this->input, function () {
|
while (($pos = strpos($buffer, "\r\n")) === false) {
|
||||||
if (feof($this->input)) {
|
$read = yield $input->read();
|
||||||
// If stream_select reported a status change for this stream,
|
if ($read === null) {
|
||||||
// but the stream is EOF, it means it was closed.
|
return;
|
||||||
$this->emit('close');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (($c = fgetc($this->input)) !== false && $c !== '') {
|
|
||||||
$this->buffer .= $c;
|
|
||||||
switch ($this->parsingMode) {
|
|
||||||
case self::PARSE_HEADERS:
|
|
||||||
if ($this->buffer === "\r\n") {
|
|
||||||
$this->parsingMode = self::PARSE_BODY;
|
|
||||||
$this->contentLength = (int)$this->headers['Content-Length'];
|
|
||||||
$this->buffer = '';
|
|
||||||
} else if (substr($this->buffer, -2) === "\r\n") {
|
|
||||||
$parts = explode(':', $this->buffer);
|
|
||||||
$this->headers[$parts[0]] = trim($parts[1]);
|
|
||||||
$this->buffer = '';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case self::PARSE_BODY:
|
|
||||||
if (strlen($this->buffer) === $this->contentLength) {
|
|
||||||
$msg = new Message(MessageBody::parse($this->buffer), $this->headers);
|
|
||||||
$this->emit('message', [$msg]);
|
|
||||||
$this->parsingMode = self::PARSE_HEADERS;
|
|
||||||
$this->headers = [];
|
|
||||||
$this->buffer = '';
|
|
||||||
}
|
}
|
||||||
|
$buffer .= $read;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerLine = substr($buffer, 0, $pos);
|
||||||
|
$buffer = substr($buffer, (int)$pos + 2);
|
||||||
|
if (!$headerLine) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
$headerPairs = \explode(': ', $headerLine);
|
||||||
|
$headers[$headerPairs[0]] = $headerPairs[1];
|
||||||
}
|
}
|
||||||
|
$contentLength = (int)$headers['Content-Length'];
|
||||||
|
while (strlen($buffer) < $contentLength) {
|
||||||
|
$read = yield $this->input->read();
|
||||||
|
if ($read === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$buffer .= $read;
|
||||||
|
}
|
||||||
|
$body = substr($buffer, 0, $contentLength);
|
||||||
|
$buffer = substr($buffer, $contentLength);
|
||||||
|
$msg = new Message(MessageBody::parse($body), $headers);
|
||||||
|
$this->emit(new MessageEvent('message', $msg));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use Amp\ByteStream\OutputStream;
|
||||||
use LanguageServer\Message;
|
use LanguageServer\Message;
|
||||||
use Sabre\Event\{
|
use Sabre\Event\{
|
||||||
Loop,
|
Loop,
|
||||||
|
@ -12,7 +13,7 @@ use Sabre\Event\{
|
||||||
class ProtocolStreamWriter implements ProtocolWriter
|
class ProtocolStreamWriter implements ProtocolWriter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var resource $output
|
* @var OutputStream $output
|
||||||
*/
|
*/
|
||||||
private $output;
|
private $output;
|
||||||
|
|
||||||
|
@ -22,9 +23,9 @@ class ProtocolStreamWriter implements ProtocolWriter
|
||||||
private $messages = [];
|
private $messages = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $output
|
* @param OutputStream $output
|
||||||
*/
|
*/
|
||||||
public function __construct($output)
|
public function __construct(OutputStream $output)
|
||||||
{
|
{
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
}
|
}
|
||||||
|
@ -32,21 +33,9 @@ class ProtocolStreamWriter implements ProtocolWriter
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function write(Message $msg): Promise
|
public function write(Message $msg): \Generator
|
||||||
{
|
{
|
||||||
// if the message queue is currently empty, register a write handler.
|
yield $this->output->write((string)$msg);
|
||||||
if (empty($this->messages)) {
|
|
||||||
Loop\addWriteStream($this->output, function () {
|
|
||||||
$this->flush();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise = new Promise();
|
|
||||||
$this->messages[] = [
|
|
||||||
'message' => (string)$msg,
|
|
||||||
'promise' => $promise
|
|
||||||
];
|
|
||||||
return $promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,5 +14,5 @@ interface ProtocolWriter
|
||||||
* @param Message $msg
|
* @param Message $msg
|
||||||
* @return Promise Resolved when the message has been fully written out to the output stream
|
* @return Promise Resolved when the message has been fully written out to the output stream
|
||||||
*/
|
*/
|
||||||
public function write(Message $msg): Promise;
|
public function write(Message $msg): \Generator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Server;
|
namespace LanguageServer\Server;
|
||||||
|
|
||||||
use LanguageServer\{
|
use Amp\Coroutine;
|
||||||
CompletionProvider, SignatureHelpProvider, LanguageClient, PhpDocument, PhpDocumentLoader, DefinitionResolver
|
use Amp\Deferred;
|
||||||
};
|
use Amp\Loop;
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use Amp\Promise;
|
||||||
|
use LanguageServer\{CompletionProvider,
|
||||||
|
DefinitionResolver,
|
||||||
|
LanguageClient,
|
||||||
|
PhpDocument,
|
||||||
|
PhpDocumentLoader,
|
||||||
|
SignatureHelpProvider};
|
||||||
use LanguageServer\Factory\LocationFactory;
|
use LanguageServer\Factory\LocationFactory;
|
||||||
use LanguageServer\Factory\RangeFactory;
|
use LanguageServer\Factory\RangeFactory;
|
||||||
use LanguageServerProtocol\{
|
use LanguageServer\Index\ReadableIndex;
|
||||||
FormattingOptions,
|
use LanguageServerProtocol\{CompletionContext,
|
||||||
Hover,
|
Hover,
|
||||||
Location,
|
|
||||||
MarkedString,
|
MarkedString,
|
||||||
|
PackageDescriptor,
|
||||||
Position,
|
Position,
|
||||||
Range,
|
|
||||||
ReferenceContext,
|
ReferenceContext,
|
||||||
SymbolDescriptor,
|
SymbolDescriptor,
|
||||||
PackageDescriptor,
|
|
||||||
SymbolLocationInformation,
|
SymbolLocationInformation,
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
TextDocumentItem,
|
TextDocumentItem,
|
||||||
VersionedTextDocumentIdentifier,
|
VersionedTextDocumentIdentifier};
|
||||||
CompletionContext
|
|
||||||
};
|
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
use Sabre\Event\Promise;
|
use function LanguageServer\{getPackageName, isVendored};
|
||||||
use Sabre\Uri;
|
use function League\Uri\parse;
|
||||||
use function LanguageServer\{
|
|
||||||
isVendored, waitForEvent, getPackageName
|
|
||||||
};
|
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all textDocument/* methods
|
* Provides method handlers for all textDocument/* methods
|
||||||
|
@ -115,13 +113,18 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
|
public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
|
||||||
{
|
{
|
||||||
return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) {
|
$deferred = new Deferred();
|
||||||
|
Loop::defer(function () use ($textDocument, $deferred) {
|
||||||
|
/** @var PhpDocument $document */
|
||||||
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
|
|
||||||
$symbols = [];
|
$symbols = [];
|
||||||
foreach ($document->getDefinitions() as $fqn => $definition) {
|
foreach ($document->getDefinitions() as $fqn => $definition) {
|
||||||
$symbols[] = $definition->symbolInformation;
|
$symbols[] = $definition->symbolInformation;
|
||||||
}
|
}
|
||||||
return $symbols;
|
$deferred->resolve($symbols);
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,10 +137,12 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function didOpen(TextDocumentItem $textDocument)
|
public function didOpen(TextDocumentItem $textDocument)
|
||||||
{
|
{
|
||||||
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
|
Loop::defer(function () use ($textDocument) {
|
||||||
if (!isVendored($document, $this->composerJson)) {
|
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
|
||||||
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
if (!isVendored($document, $this->composerJson)) {
|
||||||
}
|
yield from $this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,13 +150,18 @@ class TextDocument
|
||||||
*
|
*
|
||||||
* @param \LanguageServerProtocol\VersionedTextDocumentIdentifier $textDocument
|
* @param \LanguageServerProtocol\VersionedTextDocumentIdentifier $textDocument
|
||||||
* @param \LanguageServerProtocol\TextDocumentContentChangeEvent[] $contentChanges
|
* @param \LanguageServerProtocol\TextDocumentContentChangeEvent[] $contentChanges
|
||||||
* @return void
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
|
||||||
{
|
{
|
||||||
$document = $this->documentLoader->get($textDocument->uri);
|
$deferred = new Deferred();
|
||||||
$document->updateContent($contentChanges[0]->text);
|
Loop::defer(function () use ($deferred, $textDocument, $contentChanges) {
|
||||||
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
$document = $this->documentLoader->get($textDocument->uri);
|
||||||
|
$document->updateContent($contentChanges[0]->text);
|
||||||
|
yield from $this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
||||||
|
$deferred->resolve();
|
||||||
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,8 +189,9 @@ class TextDocument
|
||||||
TextDocumentIdentifier $textDocument,
|
TextDocumentIdentifier $textDocument,
|
||||||
Position $position
|
Position $position
|
||||||
): Promise {
|
): Promise {
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
$deferred = new Deferred();
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
Loop::defer(function () use ($deferred, $textDocument, $position) {
|
||||||
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -189,8 +200,7 @@ class TextDocument
|
||||||
// Variables always stay in the boundary of the file and need to be searched inside their function scope
|
// Variables always stay in the boundary of the file and need to be searched inside their function scope
|
||||||
// by traversing the AST
|
// by traversing the AST
|
||||||
if (
|
if (
|
||||||
|
($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration))
|
||||||
($node instanceof Node\Expression\Variable && !($node->getParent()->getParent() instanceof Node\PropertyDeclaration))
|
|
||||||
|| $node instanceof Node\Parameter
|
|| $node instanceof Node\Parameter
|
||||||
|| $node instanceof Node\UseVariableName
|
|| $node instanceof Node\UseVariableName
|
||||||
) {
|
) {
|
||||||
|
@ -217,21 +227,18 @@ class TextDocument
|
||||||
// Definition with a global FQN
|
// Definition with a global FQN
|
||||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||||
|
|
||||||
// Wait until indexing finished
|
|
||||||
if (!$this->index->isComplete()) {
|
|
||||||
yield waitForEvent($this->index, 'complete');
|
|
||||||
}
|
|
||||||
if ($fqn === null) {
|
if ($fqn === null) {
|
||||||
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($node);
|
||||||
if ($fqn === null) {
|
if ($fqn === null) {
|
||||||
return [];
|
$deferred->resolve([]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$refDocumentPromises = [];
|
$refDocumentPromises = [];
|
||||||
foreach ($this->index->getReferenceUris($fqn) as $uri) {
|
foreach ($this->index->getReferenceUris($fqn) as $uri) {
|
||||||
$refDocumentPromises[] = $this->documentLoader->getOrLoad($uri);
|
$refDocumentPromises[] = new Coroutine($this->documentLoader->getOrLoad($uri));
|
||||||
}
|
}
|
||||||
$refDocuments = yield Promise\all($refDocumentPromises);
|
$refDocuments = yield \Amp\Promise\all($refDocumentPromises);
|
||||||
foreach ($refDocuments as $document) {
|
foreach ($refDocuments as $document) {
|
||||||
$refs = $document->getReferenceNodesByFqn($fqn);
|
$refs = $document->getReferenceNodesByFqn($fqn);
|
||||||
if ($refs !== null) {
|
if ($refs !== null) {
|
||||||
|
@ -241,8 +248,9 @@ class TextDocument
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $locations;
|
$deferred->resolve($locations);
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -250,16 +258,20 @@ class TextDocument
|
||||||
* cursor position.
|
* cursor position.
|
||||||
*
|
*
|
||||||
* @param TextDocumentIdentifier $textDocument The text document
|
* @param TextDocumentIdentifier $textDocument The text document
|
||||||
* @param Position $position The position inside the text document
|
* @param Position $position The position inside the text document
|
||||||
*
|
*
|
||||||
* @return Promise <SignatureHelp>
|
* @return Promise <SignatureHelp>
|
||||||
*/
|
*/
|
||||||
public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
$deferred = new Deferred();
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
Loop::defer(function () use ($deferred, $textDocument, $position) {
|
||||||
return $this->signatureHelpProvider->getSignatureHelp($document, $position);
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
|
$deferred->resolve(
|
||||||
|
yield from $this->signatureHelpProvider->getSignatureHelp($document, $position)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -269,11 +281,13 @@ class TextDocument
|
||||||
* @param TextDocumentIdentifier $textDocument The text document
|
* @param TextDocumentIdentifier $textDocument The text document
|
||||||
* @param Position $position The position inside the text document
|
* @param Position $position The position inside the text document
|
||||||
* @return Promise <Location|Location[]>
|
* @return Promise <Location|Location[]>
|
||||||
|
* @throws \LanguageServer\ContentTooLargeException
|
||||||
*/
|
*/
|
||||||
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
$deferred = new Deferred();
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
Loop::defer(function () use ($deferred, $textDocument, $position) {
|
||||||
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -291,17 +305,18 @@ class TextDocument
|
||||||
if ($def !== null || $this->index->isComplete()) {
|
if ($def !== null || $this->index->isComplete()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
yield waitForEvent($this->index, 'definition-added');
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
$def === null
|
$def === null
|
||||||
|| $def->symbolInformation === null
|
|| $def->symbolInformation === null
|
||||||
|| Uri\parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs'
|
|| parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs'
|
||||||
) {
|
) {
|
||||||
return [];
|
$deferred->resolve([]);
|
||||||
|
} else {
|
||||||
|
$deferred->resolve($def->symbolInformation->location);
|
||||||
}
|
}
|
||||||
return $def->symbolInformation->location;
|
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -313,8 +328,9 @@ class TextDocument
|
||||||
*/
|
*/
|
||||||
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
$deferred = new Deferred();
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
Loop::defer(function () use ($deferred, $textDocument, $position) {
|
||||||
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
// Find the node under the cursor
|
// Find the node under the cursor
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
|
@ -333,7 +349,6 @@ class TextDocument
|
||||||
if ($def !== null || $this->index->isComplete()) {
|
if ($def !== null || $this->index->isComplete()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
yield waitForEvent($this->index, 'definition-added');
|
|
||||||
}
|
}
|
||||||
$range = RangeFactory::fromNode($node);
|
$range = RangeFactory::fromNode($node);
|
||||||
if ($def === null) {
|
if ($def === null) {
|
||||||
|
@ -346,8 +361,9 @@ class TextDocument
|
||||||
if ($def->documentation) {
|
if ($def->documentation) {
|
||||||
$contents[] = $def->documentation;
|
$contents[] = $def->documentation;
|
||||||
}
|
}
|
||||||
return new Hover($contents, $range);
|
$deferred->resolve(new Hover($contents, $range));
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,17 +376,20 @@ class TextDocument
|
||||||
* interface then a 'completionItem/resolve' request is sent with the selected completion item as a param. The
|
* interface then a 'completionItem/resolve' request is sent with the selected completion item as a param. The
|
||||||
* returned completion item should have the documentation property filled in.
|
* returned completion item should have the documentation property filled in.
|
||||||
*
|
*
|
||||||
* @param TextDocumentIdentifier The text document
|
* @param TextDocumentIdentifier $textDocument
|
||||||
* @param Position $position The position
|
* @param Position $position The position
|
||||||
* @param CompletionContext|null $context The completion context
|
* @param CompletionContext|null $context The completion context
|
||||||
* @return Promise <CompletionItem[]|CompletionList>
|
* @return Promise <CompletionItem[]|CompletionList>
|
||||||
*/
|
*/
|
||||||
public function completion(TextDocumentIdentifier $textDocument, Position $position, CompletionContext $context = null): Promise
|
public function completion(TextDocumentIdentifier $textDocument, Position $position, CompletionContext $context = null): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position, $context) {
|
$deferred = new Deferred();
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
Loop::defer(function () use ($deferred, $context, $position, $textDocument) {
|
||||||
return $this->completionProvider->provideCompletion($document, $position, $context);
|
/** @var PhpDocument $document */
|
||||||
|
$document = yield from $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
|
$deferred->resolve($this->completionProvider->provideCompletion($document, $position, $context));
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -382,12 +401,13 @@ class TextDocument
|
||||||
* but still may know some information about it.
|
* but still may know some information about it.
|
||||||
*
|
*
|
||||||
* @param TextDocumentIdentifier $textDocument The text document
|
* @param TextDocumentIdentifier $textDocument The text document
|
||||||
* @param Position $position The position inside the text document
|
* @param Position $position The position inside the text document
|
||||||
* @return Promise <SymbolLocationInformation[]>
|
* @return Promise <SymbolLocationInformation[]>
|
||||||
*/
|
*/
|
||||||
public function xdefinition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function xdefinition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($textDocument, $position) {
|
$deferred = new Deferred();
|
||||||
|
Loop::defer(function () use ($deferred, $textDocument, $position) {
|
||||||
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
$document = yield $this->documentLoader->getOrLoad($textDocument->uri);
|
||||||
$node = $document->getNodeAtPosition($position);
|
$node = $document->getNodeAtPosition($position);
|
||||||
if ($node === null) {
|
if ($node === null) {
|
||||||
|
@ -406,12 +426,11 @@ class TextDocument
|
||||||
if ($def !== null || $this->index->isComplete()) {
|
if ($def !== null || $this->index->isComplete()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
yield waitForEvent($this->index, 'definition-added');
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
$def === null
|
$def === null
|
||||||
|| $def->symbolInformation === null
|
|| $def->symbolInformation === null
|
||||||
|| Uri\parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs'
|
|| parse($def->symbolInformation->location->uri)['scheme'] === 'phpstubs'
|
||||||
) {
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -422,7 +441,8 @@ class TextDocument
|
||||||
$packageName = $this->composerJson->name;
|
$packageName = $this->composerJson->name;
|
||||||
}
|
}
|
||||||
$descriptor = new SymbolDescriptor($def->fqn, new PackageDescriptor($packageName));
|
$descriptor = new SymbolDescriptor($def->fqn, new PackageDescriptor($packageName));
|
||||||
return [new SymbolLocationInformation($descriptor, $def->symbolInformation->location)];
|
$deferred->resolve([new SymbolLocationInformation($descriptor, $def->symbolInformation->location)]);
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Server;
|
namespace LanguageServer\Server;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Delayed;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
use LanguageServer\{LanguageClient, PhpDocumentLoader};
|
use LanguageServer\{LanguageClient, PhpDocumentLoader};
|
||||||
use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index};
|
|
||||||
use LanguageServer\Factory\LocationFactory;
|
use LanguageServer\Factory\LocationFactory;
|
||||||
use LanguageServerProtocol\{
|
use LanguageServer\Index\{DependenciesIndex, Index, ProjectIndex};
|
||||||
FileChangeType,
|
use LanguageServerProtocol\{DependencyReference, FileChangeType, FileEvent, ReferenceInformation, SymbolDescriptor};
|
||||||
FileEvent,
|
|
||||||
SymbolInformation,
|
|
||||||
SymbolDescriptor,
|
|
||||||
ReferenceInformation,
|
|
||||||
DependencyReference,
|
|
||||||
Location
|
|
||||||
};
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
use function LanguageServer\waitForEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all workspace/* methods
|
* Provides method handlers for all workspace/* methods
|
||||||
|
@ -57,12 +51,12 @@ class Workspace
|
||||||
public $documentLoader;
|
public $documentLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param LanguageClient $client LanguageClient instance used to signal updated results
|
* @param LanguageClient $client LanguageClient instance used to signal updated results
|
||||||
* @param ProjectIndex $projectIndex Index that is used to wait for full index completeness
|
* @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 $projectIndex, 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)
|
||||||
{
|
{
|
||||||
|
@ -83,19 +77,13 @@ class Workspace
|
||||||
*/
|
*/
|
||||||
public function symbol(string $query): Promise
|
public function symbol(string $query): Promise
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($query) {
|
$symbols = [];
|
||||||
// Wait until indexing for definitions finished
|
foreach ($this->sourceIndex->getDefinitions() as $fqn => $definition) {
|
||||||
if (!$this->sourceIndex->isStaticComplete()) {
|
if ($query === '' || stripos($fqn, $query) !== false) {
|
||||||
yield waitForEvent($this->sourceIndex, 'static-complete');
|
$symbols[] = $definition->symbolInformation;
|
||||||
}
|
}
|
||||||
$symbols = [];
|
}
|
||||||
foreach ($this->sourceIndex->getDefinitions() as $fqn => $definition) {
|
return new Success($symbols);
|
||||||
if ($query === '' || stripos($fqn, $query) !== false) {
|
|
||||||
$symbols[] = $definition->symbolInformation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $symbols;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,35 +94,35 @@ class Workspace
|
||||||
*/
|
*/
|
||||||
public function didChangeWatchedFiles(array $changes)
|
public function didChangeWatchedFiles(array $changes)
|
||||||
{
|
{
|
||||||
foreach ($changes as $change) {
|
Loop::defer(function () use ($changes) {
|
||||||
if ($change->type === FileChangeType::DELETED) {
|
foreach ($changes as $change) {
|
||||||
$this->client->textDocument->publishDiagnostics($change->uri, []);
|
if ($change->type === FileChangeType::DELETED) {
|
||||||
|
yield from $this->client->textDocument->publishDiagnostics($change->uri, []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata.
|
* The workspace references request is sent from the client to the server to locate project-wide references to a symbol given its description / metadata.
|
||||||
*
|
*
|
||||||
* @param SymbolDescriptor $query Partial metadata about the symbol that is being searched for.
|
* @param SymbolDescriptor $query Partial metadata about the symbol that is being searched for.
|
||||||
* @param string[] $files An optional list of files to restrict the search to.
|
* @param string[] $files An optional list of files to restrict the search to.
|
||||||
* @return ReferenceInformation[]
|
* @return \Generator
|
||||||
|
* @throws \LanguageServer\ContentTooLargeException
|
||||||
*/
|
*/
|
||||||
public function xreferences($query, array $files = null): Promise
|
public function xreferences($query, array $files = null): \Generator
|
||||||
{
|
{
|
||||||
// TODO: $files is unused in the coroutine
|
$deferred = new Deferred();
|
||||||
return coroutine(function () use ($query, $files) {
|
Loop::defer(function () use ($deferred, $query, $files) {
|
||||||
|
// TODO: $files is unused in the coroutine
|
||||||
if ($this->composerLock === null) {
|
if ($this->composerLock === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// Wait until indexing finished
|
|
||||||
if (!$this->projectIndex->isComplete()) {
|
|
||||||
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 = [];
|
||||||
// Get all references TO dependencies
|
// Get all references TO dependencies
|
||||||
$fqns = isset($query->fqsen) ? [$query->fqsen] : array_values($this->dependenciesIndex->getDefinitions());
|
$fqns = isset($query->fqsen) ? [$query->fqsen] : array_values(yield from $this->dependenciesIndex->getDefinitions());
|
||||||
foreach ($fqns as $fqn) {
|
foreach ($fqns as $fqn) {
|
||||||
foreach ($this->sourceIndex->getReferenceUris($fqn) as $uri) {
|
foreach ($this->sourceIndex->getReferenceUris($fqn) as $uri) {
|
||||||
if (!isset($refs[$uri])) {
|
if (!isset($refs[$uri])) {
|
||||||
|
@ -148,7 +136,7 @@ class Workspace
|
||||||
$refInfos = [];
|
$refInfos = [];
|
||||||
foreach ($refs as $uri => $fqns) {
|
foreach ($refs as $uri => $fqns) {
|
||||||
foreach ($fqns as $fqn) {
|
foreach ($fqns as $fqn) {
|
||||||
$doc = yield $this->documentLoader->getOrLoad($uri);
|
$doc = yield from $this->documentLoader->getOrLoad($uri);
|
||||||
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
|
foreach ($doc->getReferenceNodesByFqn($fqn) as $node) {
|
||||||
$refInfo = new ReferenceInformation;
|
$refInfo = new ReferenceInformation;
|
||||||
$refInfo->reference = LocationFactory::fromNode($node);
|
$refInfo->reference = LocationFactory::fromNode($node);
|
||||||
|
@ -157,8 +145,9 @@ class Workspace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $refInfos;
|
$deferred->resolve($refInfos);
|
||||||
});
|
});
|
||||||
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
|
use Amp\Delayed;
|
||||||
use LanguageServer\Index\ReadableIndex;
|
use LanguageServer\Index\ReadableIndex;
|
||||||
use LanguageServerProtocol\{
|
use LanguageServerProtocol\{Position, SignatureHelp};
|
||||||
Position,
|
|
||||||
SignatureHelp
|
|
||||||
};
|
|
||||||
use Microsoft\PhpParser\Node;
|
use Microsoft\PhpParser\Node;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
|
||||||
|
|
||||||
class SignatureHelpProvider
|
class SignatureHelpProvider
|
||||||
{
|
{
|
||||||
|
@ -27,8 +24,8 @@ class SignatureHelpProvider
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param DefinitionResolver $definitionResolver
|
* @param DefinitionResolver $definitionResolver
|
||||||
* @param ReadableIndex $index
|
* @param ReadableIndex $index
|
||||||
* @param PhpDocumentLoader $documentLoader
|
* @param PhpDocumentLoader $documentLoader
|
||||||
*/
|
*/
|
||||||
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index, PhpDocumentLoader $documentLoader)
|
public function __construct(DefinitionResolver $definitionResolver, ReadableIndex $index, PhpDocumentLoader $documentLoader)
|
||||||
{
|
{
|
||||||
|
@ -40,31 +37,29 @@ class SignatureHelpProvider
|
||||||
/**
|
/**
|
||||||
* Finds signature help for a callable position
|
* Finds signature help for a callable position
|
||||||
*
|
*
|
||||||
* @param PhpDocument $doc The document the position belongs to
|
* @param PhpDocument $doc The document the position belongs to
|
||||||
* @param Position $position The position to detect a call from
|
* @param Position $position The position to detect a call from
|
||||||
*
|
*
|
||||||
* @return Promise <SignatureHelp>
|
* @return Promise <SignatureHelp>
|
||||||
*/
|
*/
|
||||||
public function getSignatureHelp(PhpDocument $doc, Position $position): Promise
|
public function getSignatureHelp(PhpDocument $doc, Position $position): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($doc, $position) {
|
// Find the node under the cursor
|
||||||
// Find the node under the cursor
|
$node = $doc->getNodeAtPosition($position);
|
||||||
$node = $doc->getNodeAtPosition($position);
|
|
||||||
|
|
||||||
// Find the definition of the item being called
|
// Find the definition of the item being called
|
||||||
list($def, $argumentExpressionList) = yield $this->getCallingInfo($node);
|
list($def, $argumentExpressionList) = yield from $this->getCallingInfo($node);
|
||||||
|
|
||||||
if (!$def || !$def->signatureInformation) {
|
if (!$def || !$def->signatureInformation) {
|
||||||
return new SignatureHelp();
|
return new SignatureHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the active parameter
|
// Find the active parameter
|
||||||
$activeParam = $argumentExpressionList
|
$activeParam = $argumentExpressionList
|
||||||
? $this->findActiveParameter($argumentExpressionList, $position, $doc)
|
? $this->findActiveParameter($argumentExpressionList, $position, $doc)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return new SignatureHelp([$def->signatureInformation], 0, $activeParam);
|
return new SignatureHelp([$def->signatureInformation], 0, $activeParam);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,83 +68,81 @@ class SignatureHelpProvider
|
||||||
*
|
*
|
||||||
* @param Node $node The node to find calling information from
|
* @param Node $node The node to find calling information from
|
||||||
*
|
*
|
||||||
* @return Promise <array|null>
|
* @return \Generator <array|null>
|
||||||
*/
|
*/
|
||||||
private function getCallingInfo(Node $node)
|
private function getCallingInfo(Node $node): \Generator
|
||||||
{
|
{
|
||||||
return coroutine(function () use ($node) {
|
$fqn = null;
|
||||||
$fqn = null;
|
$callingNode = null;
|
||||||
$callingNode = null;
|
if ($node instanceof Node\DelimitedList\ArgumentExpressionList) {
|
||||||
if ($node instanceof Node\DelimitedList\ArgumentExpressionList) {
|
// Cursor is already inside a (
|
||||||
// Cursor is already inside a (
|
$argumentExpressionList = $node;
|
||||||
$argumentExpressionList = $node;
|
if ($node->parent instanceof Node\Expression\ObjectCreationExpression) {
|
||||||
if ($node->parent instanceof Node\Expression\ObjectCreationExpression) {
|
// Constructing something
|
||||||
// Constructing something
|
$callingNode = $node->parent->classTypeDesignator;
|
||||||
$callingNode = $node->parent->classTypeDesignator;
|
|
||||||
if (!$callingNode instanceof Node\QualifiedName) {
|
|
||||||
// We only support constructing from a QualifiedName
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode);
|
|
||||||
$fqn = "{$fqn}->__construct()";
|
|
||||||
} else {
|
|
||||||
$callingNode = $node->parent->getFirstChildNode(
|
|
||||||
Node\Expression\MemberAccessExpression::class,
|
|
||||||
Node\Expression\ScopedPropertyAccessExpression::class,
|
|
||||||
Node\QualifiedName::class
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif ($node instanceof Node\Expression\CallExpression) {
|
|
||||||
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
|
||||||
$callingNode = $node->getFirstChildNode(
|
|
||||||
Node\Expression\MemberAccessExpression::class,
|
|
||||||
Node\Expression\ScopedPropertyAccessExpression::class,
|
|
||||||
Node\QualifiedName::class
|
|
||||||
);
|
|
||||||
} elseif ($node instanceof Node\Expression\ObjectCreationExpression) {
|
|
||||||
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
|
||||||
$callingNode = $node->classTypeDesignator;
|
|
||||||
if (!$callingNode instanceof Node\QualifiedName) {
|
if (!$callingNode instanceof Node\QualifiedName) {
|
||||||
// We only support constructing from a QualifiedName
|
// We only support constructing from a QualifiedName
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Manually build the __construct fqn
|
|
||||||
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode);
|
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode);
|
||||||
$fqn = "{$fqn}->__construct()";
|
$fqn = "{$fqn}->__construct()";
|
||||||
|
} else {
|
||||||
|
$callingNode = $node->parent->getFirstChildNode(
|
||||||
|
Node\Expression\MemberAccessExpression::class,
|
||||||
|
Node\Expression\ScopedPropertyAccessExpression::class,
|
||||||
|
Node\QualifiedName::class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
} elseif ($node instanceof Node\Expression\CallExpression) {
|
||||||
if (!$callingNode) {
|
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
||||||
|
$callingNode = $node->getFirstChildNode(
|
||||||
|
Node\Expression\MemberAccessExpression::class,
|
||||||
|
Node\Expression\ScopedPropertyAccessExpression::class,
|
||||||
|
Node\QualifiedName::class
|
||||||
|
);
|
||||||
|
} elseif ($node instanceof Node\Expression\ObjectCreationExpression) {
|
||||||
|
$argumentExpressionList = $node->getFirstChildNode(Node\DelimitedList\ArgumentExpressionList::class);
|
||||||
|
$callingNode = $node->classTypeDesignator;
|
||||||
|
if (!$callingNode instanceof Node\QualifiedName) {
|
||||||
|
// We only support constructing from a QualifiedName
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// Manually build the __construct fqn
|
||||||
|
$fqn = $this->definitionResolver->resolveReferenceNodeToFqn($callingNode);
|
||||||
|
$fqn = "{$fqn}->__construct()";
|
||||||
|
}
|
||||||
|
|
||||||
// Now find the definition of the call
|
if (!$callingNode) {
|
||||||
$fqn = $fqn ?: DefinitionResolver::getDefinedFqn($callingNode);
|
return null;
|
||||||
while (true) {
|
}
|
||||||
if ($fqn) {
|
|
||||||
$def = $this->index->getDefinition($fqn);
|
// Now find the definition of the call
|
||||||
} else {
|
$fqn = $fqn ?: DefinitionResolver::getDefinedFqn($callingNode);
|
||||||
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($callingNode);
|
while (true) {
|
||||||
}
|
if ($fqn) {
|
||||||
if ($def !== null || $this->index->isComplete()) {
|
$def = $this->index->getDefinition($fqn);
|
||||||
break;
|
} else {
|
||||||
}
|
$def = $this->definitionResolver->resolveReferenceNodeToDefinition($callingNode);
|
||||||
yield waitForEvent($this->index, 'definition-added');
|
|
||||||
}
|
}
|
||||||
|
if ($def !== null || $this->index->isComplete()) {
|
||||||
if (!$def) {
|
break;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [$def, $argumentExpressionList];
|
if (!$def) {
|
||||||
});
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Delayed(0);
|
||||||
|
return [$def, $argumentExpressionList];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a position and arguments, finds the "active" argument at the position
|
* Given a position and arguments, finds the "active" argument at the position
|
||||||
*
|
*
|
||||||
* @param Node\DelimitedList\ArgumentExpressionList $argumentExpressionList The argument expression list
|
* @param Node\DelimitedList\ArgumentExpressionList $argumentExpressionList The argument expression list
|
||||||
* @param Position $position The position to detect the active argument from
|
* @param Position $position The position to detect the active argument from
|
||||||
* @param PhpDocument $doc The document that contains the expression
|
* @param PhpDocument $doc The document that contains the expression
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer;
|
namespace LanguageServer;
|
||||||
|
|
||||||
use Throwable;
|
use Amp\Loop;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Sabre\Event\{Loop, Promise, EmitterInterface};
|
use Throwable;
|
||||||
use Sabre\Uri;
|
use function League\Uri\parse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms an absolute file path into a URI as used by the language server protocol.
|
* Transforms an absolute file path into a URI as used by the language server protocol.
|
||||||
|
@ -60,39 +60,11 @@ function uriToPath(string $uri)
|
||||||
*/
|
*/
|
||||||
function crash(Throwable $err)
|
function crash(Throwable $err)
|
||||||
{
|
{
|
||||||
Loop\nextTick(function () use ($err) {
|
Loop::run(function () use ($err) {
|
||||||
throw $err;
|
throw $err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a promise that is resolved after x seconds.
|
|
||||||
* Useful for giving back control to the event loop inside a coroutine.
|
|
||||||
*
|
|
||||||
* @param int $seconds
|
|
||||||
* @return Promise <void>
|
|
||||||
*/
|
|
||||||
function timeout($seconds = 0): Promise
|
|
||||||
{
|
|
||||||
$promise = new Promise;
|
|
||||||
Loop\setTimeout([$promise, 'fulfill'], $seconds);
|
|
||||||
return $promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a promise that is fulfilled once the passed event was triggered on the passed EventEmitter
|
|
||||||
*
|
|
||||||
* @param EmitterInterface $emitter
|
|
||||||
* @param string $event
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
function waitForEvent(EmitterInterface $emitter, string $event): Promise
|
|
||||||
{
|
|
||||||
$p = new Promise;
|
|
||||||
$emitter->once($event, [$p, 'fulfill']);
|
|
||||||
return $p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the part of $b that is not overlapped by $a
|
* Returns the part of $b that is not overlapped by $a
|
||||||
* Example:
|
* Example:
|
||||||
|
@ -125,7 +97,7 @@ function stripStringOverlap(string $a, string $b): string
|
||||||
function sortUrisLevelOrder(&$uriList)
|
function sortUrisLevelOrder(&$uriList)
|
||||||
{
|
{
|
||||||
usort($uriList, function ($a, $b) {
|
usort($uriList, function ($a, $b) {
|
||||||
return substr_count(Uri\parse($a)['path'], '/') - substr_count(Uri\parse($b)['path'], '/');
|
return substr_count(parse($a)['path'], '/') - substr_count(parse($b)['path'], '/');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,13 +105,13 @@ function sortUrisLevelOrder(&$uriList)
|
||||||
* Checks a document against the composer.json to see if it
|
* Checks a document against the composer.json to see if it
|
||||||
* is a vendored document
|
* is a vendored document
|
||||||
*
|
*
|
||||||
* @param PhpDocument $document
|
* @param PhpDocument $document
|
||||||
* @param \stdClass|null $composerJson
|
* @param \stdClass|null $composerJson
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool
|
function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool
|
||||||
{
|
{
|
||||||
$path = Uri\parse($document->getUri())['path'];
|
$path = parse($document->getUri())['path'];
|
||||||
$vendorDir = getVendorDir($composerJson);
|
$vendorDir = getVendorDir($composerJson);
|
||||||
return strpos($path, "/$vendorDir/") !== false;
|
return strpos($path, "/$vendorDir/") !== false;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +120,7 @@ function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool
|
||||||
* Check a given URI against the composer.json to see if it
|
* Check a given URI against the composer.json to see if it
|
||||||
* is a vendored URI
|
* is a vendored URI
|
||||||
*
|
*
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
* @param \stdClass|null $composerJson
|
* @param \stdClass|null $composerJson
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,43 +1,48 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
namespace LanguageServer\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use LanguageServer\ClientHandler;
|
|
||||||
use LanguageServer\Message;
|
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Sabre\Event\Loop;
|
use Amp\Loop;
|
||||||
|
use LanguageServer\ClientHandler;
|
||||||
|
use LanguageServer\Event\MessageEvent;
|
||||||
|
use LanguageServer\Message;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class ClientHandlerTest extends TestCase
|
class ClientHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testRequest()
|
public function testRequest()
|
||||||
{
|
{
|
||||||
$reader = new MockProtocolStream;
|
Loop::run(function () {
|
||||||
$writer = new MockProtocolStream;
|
$reader = new MockProtocolStream;
|
||||||
$handler = new ClientHandler($reader, $writer);
|
$writer = new MockProtocolStream;
|
||||||
$writer->once('message', function (Message $msg) use ($reader) {
|
$handler = new ClientHandler($reader, $writer);
|
||||||
// Respond to request
|
$writer->addOneTimeListener('message', function (MessageEvent $messageEvent) use ($reader) {
|
||||||
Loop\setTimeout(function () use ($reader, $msg) {
|
$msg = $messageEvent->getMessage();
|
||||||
$reader->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, 'pong')));
|
// Respond to request
|
||||||
}, 0);
|
Loop::defer(function () use ($reader, $msg) {
|
||||||
});
|
yield from $reader->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, 'pong')));
|
||||||
$handler->request('testMethod', ['ping'])->then(function ($result) {
|
});
|
||||||
|
});
|
||||||
|
$result = yield from $handler->request('testMethod', ['ping']);
|
||||||
$this->assertEquals('pong', $result);
|
$this->assertEquals('pong', $result);
|
||||||
})->wait();
|
// No event listeners
|
||||||
// No event listeners
|
$this->assertEquals([], $reader->getListeners('message'));
|
||||||
$this->assertEquals([], $reader->listeners('message'));
|
$this->assertEquals([], $writer->getListeners('message'));
|
||||||
$this->assertEquals([], $writer->listeners('message'));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNotify()
|
public function testNotify()
|
||||||
{
|
{
|
||||||
$reader = new MockProtocolStream;
|
Loop::run(function () {
|
||||||
$writer = new MockProtocolStream;
|
$reader = new MockProtocolStream;
|
||||||
$handler = new ClientHandler($reader, $writer);
|
$writer = new MockProtocolStream;
|
||||||
$handler->notify('testMethod', ['ping'])->wait();
|
$handler = new ClientHandler($reader, $writer);
|
||||||
// No event listeners
|
yield from $handler->notify('testMethod', ['ping']);
|
||||||
$this->assertEquals([], $reader->listeners('message'));
|
// No event listeners
|
||||||
$this->assertEquals([], $writer->listeners('message'));
|
$this->assertEquals([], $reader->getListeners('message'));
|
||||||
|
$this->assertEquals([], $writer->getListeners('message'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,122 +1,132 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
namespace LanguageServer\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use AdvancedJsonRpc;
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Exception;
|
||||||
|
use LanguageServer\Event\MessageEvent;
|
||||||
use LanguageServer\LanguageServer;
|
use LanguageServer\LanguageServer;
|
||||||
use LanguageServer\Message;
|
use LanguageServer\Message;
|
||||||
use LanguageServerProtocol\{
|
use LanguageServerProtocol\{ClientCapabilities,
|
||||||
ClientCapabilities,
|
|
||||||
TextDocumentSyncKind,
|
|
||||||
MessageType,
|
|
||||||
TextDocumentItem,
|
|
||||||
TextDocumentIdentifier,
|
|
||||||
InitializeResult,
|
|
||||||
ServerCapabilities,
|
|
||||||
CompletionOptions,
|
CompletionOptions,
|
||||||
SignatureHelpOptions
|
InitializeResult,
|
||||||
};
|
MessageType,
|
||||||
use AdvancedJsonRpc;
|
ServerCapabilities,
|
||||||
|
SignatureHelpOptions,
|
||||||
|
TextDocumentIdentifier,
|
||||||
|
TextDocumentItem,
|
||||||
|
TextDocumentSyncKind};
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
use Webmozart\Glob\Glob;
|
use Webmozart\Glob\Glob;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use Exception;
|
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
|
||||||
class LanguageServerTest extends TestCase
|
class LanguageServerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testInitialize()
|
public function testInitialize()
|
||||||
{
|
{
|
||||||
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
Loop::run(function () {
|
||||||
$result = $server->initialize(new ClientCapabilities, __DIR__, getmypid())->wait();
|
$server = new LanguageServer(new MockProtocolStream, new MockProtocolStream);
|
||||||
|
$result = yield $server->initialize(new ClientCapabilities, __DIR__, getmypid());
|
||||||
|
|
||||||
$serverCapabilities = new ServerCapabilities();
|
$serverCapabilities = new ServerCapabilities();
|
||||||
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
|
||||||
$serverCapabilities->documentSymbolProvider = true;
|
$serverCapabilities->documentSymbolProvider = true;
|
||||||
$serverCapabilities->workspaceSymbolProvider = true;
|
$serverCapabilities->workspaceSymbolProvider = true;
|
||||||
$serverCapabilities->definitionProvider = true;
|
$serverCapabilities->definitionProvider = true;
|
||||||
$serverCapabilities->referencesProvider = true;
|
$serverCapabilities->referencesProvider = true;
|
||||||
$serverCapabilities->hoverProvider = true;
|
$serverCapabilities->hoverProvider = true;
|
||||||
$serverCapabilities->completionProvider = new CompletionOptions;
|
$serverCapabilities->completionProvider = new CompletionOptions;
|
||||||
$serverCapabilities->completionProvider->resolveProvider = false;
|
$serverCapabilities->completionProvider->resolveProvider = false;
|
||||||
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
|
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
|
||||||
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions;
|
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions;
|
||||||
$serverCapabilities->signatureHelpProvider->triggerCharacters = ['(', ','];
|
$serverCapabilities->signatureHelpProvider->triggerCharacters = ['(', ','];
|
||||||
$serverCapabilities->xworkspaceReferencesProvider = true;
|
$serverCapabilities->xworkspaceReferencesProvider = true;
|
||||||
$serverCapabilities->xdefinitionProvider = true;
|
$serverCapabilities->xdefinitionProvider = true;
|
||||||
$serverCapabilities->xdependenciesProvider = true;
|
$serverCapabilities->xdependenciesProvider = true;
|
||||||
|
|
||||||
$this->assertEquals(new InitializeResult($serverCapabilities), $result);
|
$this->assertEquals(new InitializeResult($serverCapabilities), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexingWithDirectFileAccess()
|
public function testIndexingWithDirectFileAccess()
|
||||||
{
|
{
|
||||||
$promise = new Promise;
|
Loop::run(function () {
|
||||||
$input = new MockProtocolStream;
|
$deferred = new Deferred();
|
||||||
$output = new MockProtocolStream;
|
$input = new MockProtocolStream;
|
||||||
$output->on('message', function (Message $msg) use ($promise) {
|
$output = new MockProtocolStream;
|
||||||
if ($msg->body->method === 'window/logMessage' && $promise->state === Promise::PENDING) {
|
$output->addListener('message', function (MessageEvent $messageEvent) use ($deferred) {
|
||||||
if ($msg->body->params->type === MessageType::ERROR) {
|
$msg = $messageEvent->getMessage();
|
||||||
$promise->reject(new Exception($msg->body->params->message));
|
Loop::defer(function () use ($deferred, $msg) {
|
||||||
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
if ($msg->body->method === 'window/logMessage') {
|
||||||
$promise->fulfill();
|
if ($msg->body->params->type === MessageType::ERROR) {
|
||||||
}
|
$deferred->fail(new Exception($msg->body->params->message));
|
||||||
}
|
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
||||||
|
$deferred->resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$server = new LanguageServer($input, $output);
|
||||||
|
$capabilities = new ClientCapabilities;
|
||||||
|
yield $server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
|
||||||
|
$this->assertTrue(yield $deferred->promise());
|
||||||
});
|
});
|
||||||
$server = new LanguageServer($input, $output);
|
|
||||||
$capabilities = new ClientCapabilities;
|
|
||||||
$server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
|
|
||||||
$promise->wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexingWithFilesAndContentRequests()
|
public function testIndexingWithFilesAndContentRequests()
|
||||||
{
|
{
|
||||||
$promise = new Promise;
|
Loop::run(function () {
|
||||||
$filesCalled = false;
|
$deferred = new Deferred();
|
||||||
$contentCalled = false;
|
$filesCalled = false;
|
||||||
$rootPath = realpath(__DIR__ . '/../fixtures');
|
$contentCalled = false;
|
||||||
$input = new MockProtocolStream;
|
$rootPath = realpath(__DIR__ . '/../fixtures');
|
||||||
$output = new MockProtocolStream;
|
$input = new MockProtocolStream;
|
||||||
$run = 1;
|
$output = new MockProtocolStream;
|
||||||
$output->on('message', function (Message $msg) use ($promise, $input, $rootPath, &$filesCalled, &$contentCalled, &$run) {
|
$run = 1;
|
||||||
if ($msg->body->method === 'textDocument/xcontent') {
|
$output->addListener('message', function (MessageEvent $messageEvent) use ($deferred, $input, $rootPath, &$filesCalled, &$contentCalled, &$run) {
|
||||||
// Document content requested
|
$msg = $messageEvent->getMessage();
|
||||||
$contentCalled = true;
|
Loop::defer(function () use ($msg, $deferred, $input, $rootPath, &$filesCalled, &$contentCalled, &$run) {
|
||||||
$textDocumentItem = new TextDocumentItem;
|
if ($msg->body->method === 'textDocument/xcontent') {
|
||||||
$textDocumentItem->uri = $msg->body->params->textDocument->uri;
|
// Document content requested
|
||||||
$textDocumentItem->version = 1;
|
$contentCalled = true;
|
||||||
$textDocumentItem->languageId = 'php';
|
$textDocumentItem = new TextDocumentItem;
|
||||||
$textDocumentItem->text = file_get_contents($msg->body->params->textDocument->uri);
|
$textDocumentItem->uri = $msg->body->params->textDocument->uri;
|
||||||
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $textDocumentItem)));
|
$textDocumentItem->version = 1;
|
||||||
} else if ($msg->body->method === 'workspace/xfiles') {
|
$textDocumentItem->languageId = 'php';
|
||||||
// Files requested
|
$textDocumentItem->text = file_get_contents($msg->body->params->textDocument->uri);
|
||||||
$filesCalled = true;
|
yield from $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $textDocumentItem)));
|
||||||
$pattern = Path::makeAbsolute('**/*.php', $msg->body->params->base ?? $rootPath);
|
} else if ($msg->body->method === 'workspace/xfiles') {
|
||||||
$files = [];
|
// Files requested
|
||||||
foreach (Glob::glob($pattern) as $path) {
|
$filesCalled = true;
|
||||||
$files[] = new TextDocumentIdentifier(pathToUri($path));
|
$pattern = Path::makeAbsolute('**/*.php', $msg->body->params->base ?? $rootPath);
|
||||||
}
|
$files = [];
|
||||||
$input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $files)));
|
foreach (Glob::glob($pattern) as $path) {
|
||||||
} else if ($msg->body->method === 'window/logMessage') {
|
$files[] = new TextDocumentIdentifier(pathToUri($path));
|
||||||
// Message logged
|
}
|
||||||
if ($msg->body->params->type === MessageType::ERROR) {
|
yield from $input->write(new Message(new AdvancedJsonRpc\SuccessResponse($msg->body->id, $files)));
|
||||||
// Error happened during indexing, fail test
|
} else if ($msg->body->method === 'window/logMessage') {
|
||||||
if ($promise->state === Promise::PENDING) {
|
// Message logged
|
||||||
$promise->reject(new Exception($msg->body->params->message));
|
if ($msg->body->params->type === MessageType::ERROR) {
|
||||||
|
// Error happened during indexing, fail test
|
||||||
|
$deferred->fail(new Exception($msg->body->params->message));
|
||||||
|
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
||||||
|
$deferred->resolve(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (preg_match('/All \d+ PHP files parsed/', $msg->body->params->message)) {
|
});
|
||||||
$promise->fulfill();
|
});
|
||||||
}
|
$server = new LanguageServer($input, $output);
|
||||||
}
|
$capabilities = new ClientCapabilities;
|
||||||
|
$capabilities->xfilesProvider = true;
|
||||||
|
$capabilities->xcontentProvider = true;
|
||||||
|
yield $server->initialize($capabilities, $rootPath, getmypid());
|
||||||
|
yield $deferred->promise();
|
||||||
|
$this->assertTrue($filesCalled);
|
||||||
|
$this->assertTrue($contentCalled);
|
||||||
});
|
});
|
||||||
$server = new LanguageServer($input, $output);
|
|
||||||
$capabilities = new ClientCapabilities;
|
|
||||||
$capabilities->xfilesProvider = true;
|
|
||||||
$capabilities->xcontentProvider = true;
|
|
||||||
$server->initialize($capabilities, $rootPath, getmypid());
|
|
||||||
$promise->wait();
|
|
||||||
$this->assertTrue($filesCalled);
|
|
||||||
$this->assertTrue($contentCalled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
namespace LanguageServer\Tests;
|
||||||
|
|
||||||
use LanguageServer\{ProtocolReader, ProtocolWriter};
|
use Amp\Deferred;
|
||||||
|
use Amp\Delayed;
|
||||||
|
use Amp\Loop;
|
||||||
|
use LanguageServer\{Event\MessageEvent, ProtocolReader, ProtocolWriter};
|
||||||
use LanguageServer\Message;
|
use LanguageServer\Message;
|
||||||
use Sabre\Event\{Loop, Emitter, Promise};
|
use League\Event\Emitter;
|
||||||
|
use League\Event\Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fake duplex protocol stream
|
* A fake duplex protocol stream
|
||||||
|
@ -18,11 +22,11 @@ class MockProtocolStream extends Emitter implements ProtocolReader, ProtocolWrit
|
||||||
* @param Message $msg
|
* @param Message $msg
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function write(Message $msg): Promise
|
public function write(Message $msg): \Generator
|
||||||
{
|
{
|
||||||
Loop\nextTick(function () use ($msg) {
|
Loop::defer(function () use ($msg) {
|
||||||
$this->emit('message', [Message::parse((string)$msg)]);
|
$this->emit(new MessageEvent('message', Message::parse((string)$msg)));
|
||||||
});
|
});
|
||||||
return Promise\resolve(null);
|
yield new Delayed(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server;
|
namespace LanguageServer\Tests\Server;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
PhpDocument, PhpDocumentLoader, Project, DefinitionResolver
|
PhpDocument, PhpDocumentLoader, Project, DefinitionResolver
|
||||||
};
|
};
|
||||||
|
@ -32,10 +33,12 @@ class PhpDocumentLoaderTest extends TestCase
|
||||||
|
|
||||||
public function testGetOrLoadLoadsDocument()
|
public function testGetOrLoadLoadsDocument()
|
||||||
{
|
{
|
||||||
$document = $this->loader->getOrLoad(pathToUri(__FILE__))->wait();
|
Loop::run(function () {
|
||||||
|
$document = yield from $this->loader->getOrLoad(pathToUri(__FILE__));
|
||||||
|
|
||||||
$this->assertNotNull($document);
|
$this->assertNotNull($document);
|
||||||
$this->assertInstanceOf(PhpDocument::class, $document);
|
$this->assertInstanceOf(PhpDocument::class, $document);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetReturnsOpenedInstance()
|
public function testGetReturnsOpenedInstance()
|
||||||
|
|
|
@ -1,32 +1,49 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
namespace LanguageServer\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use AdvancedJsonRpc\{Request as RequestBody};
|
||||||
use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter};
|
use Amp\ByteStream\ResourceInputStream;
|
||||||
|
use Amp\ByteStream\ResourceOutputStream;
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
|
use LanguageServer\{Event\MessageEvent, ProtocolStreamReader};
|
||||||
use LanguageServer\Message;
|
use LanguageServer\Message;
|
||||||
use AdvancedJsonRpc\{Request as RequestBody, Response as ResponseBody};
|
use PHPUnit\Framework\TestCase;
|
||||||
use Sabre\Event\Loop;
|
|
||||||
|
|
||||||
class ProtocolStreamReaderTest extends TestCase
|
class ProtocolStreamReaderTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function getStreamPair()
|
||||||
|
{
|
||||||
|
$domain = \stripos(PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX;
|
||||||
|
list($left, $right) = @\stream_socket_pair($domain, \STREAM_SOCK_STREAM, \STREAM_IPPROTO_IP);
|
||||||
|
$a = new ResourceOutputStream($left);
|
||||||
|
$b = new ResourceInputStream($right);
|
||||||
|
return [$a, $b];
|
||||||
|
}
|
||||||
|
|
||||||
public function testParsingWorksAndListenerIsCalled()
|
public function testParsingWorksAndListenerIsCalled()
|
||||||
{
|
{
|
||||||
$tmpfile = tempnam('', '');
|
Loop::run(function () {
|
||||||
$writeHandle = fopen($tmpfile, 'w');
|
/** @var ResourceOutputStream $outputStream */
|
||||||
$reader = new ProtocolStreamReader(fopen($tmpfile, 'r'));
|
/** @var ResourceInputStream $inputStream */
|
||||||
$msg = null;
|
list($outputStream, $inputStream) = $this->getStreamPair();
|
||||||
$reader->on('message', function (Message $message) use (&$msg) {
|
|
||||||
$msg = $message;
|
$reader = new ProtocolStreamReader($inputStream);
|
||||||
|
$deferred = new Deferred();
|
||||||
|
$reader->addListener('message', function (MessageEvent $messageEvent) use (&$deferred) {
|
||||||
|
$deferred->resolve($messageEvent->getMessage());
|
||||||
|
});
|
||||||
|
|
||||||
|
yield $outputStream->write((string)new Message(new RequestBody(1, 'aMethod', ['arg' => 'Hello World'])));
|
||||||
|
$msg = yield $deferred->promise();
|
||||||
|
$this->assertNotNull($msg);
|
||||||
|
$this->assertInstanceOf(Message::class, $msg);
|
||||||
|
$this->assertInstanceOf(RequestBody::class, $msg->body);
|
||||||
|
$this->assertEquals(1, $msg->body->id);
|
||||||
|
$this->assertEquals('aMethod', $msg->body->method);
|
||||||
|
$this->assertEquals((object)['arg' => 'Hello World'], $msg->body->params);
|
||||||
});
|
});
|
||||||
$ret = fwrite($writeHandle, (string)new Message(new RequestBody(1, 'aMethod', ['arg' => 'Hello World'])));
|
|
||||||
Loop\tick();
|
|
||||||
$this->assertNotNull($msg);
|
|
||||||
$this->assertInstanceOf(Message::class, $msg);
|
|
||||||
$this->assertInstanceOf(RequestBody::class, $msg->body);
|
|
||||||
$this->assertEquals(1, $msg->body->id);
|
|
||||||
$this->assertEquals('aMethod', $msg->body->method);
|
|
||||||
$this->assertEquals((object)['arg' => 'Hello World'], $msg->body->params);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace LanguageServer\Tests;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use LanguageServer\ProtocolStreamWriter;
|
|
||||||
use LanguageServer\Message;
|
|
||||||
use AdvancedJsonRpc\{Request as RequestBody};
|
|
||||||
use Sabre\Event\Loop;
|
|
||||||
|
|
||||||
class ProtocolStreamWriterTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testLargeMessageIsSent()
|
|
||||||
{
|
|
||||||
$tmpfile = tempnam('', '');
|
|
||||||
$writeHandle = fopen($tmpfile, 'w');
|
|
||||||
|
|
||||||
stream_set_blocking($writeHandle, false);
|
|
||||||
|
|
||||||
$writer = new ProtocolStreamWriter($writeHandle);
|
|
||||||
$msg = new Message(new RequestBody(1, 'aMethod', ['arg' => str_repeat('X', 100000)]));
|
|
||||||
$msgString = (string)$msg;
|
|
||||||
|
|
||||||
$promise = $writer->write($msg);
|
|
||||||
|
|
||||||
Loop\tick();
|
|
||||||
|
|
||||||
$promise->wait();
|
|
||||||
|
|
||||||
fclose($writeHandle);
|
|
||||||
|
|
||||||
$this->assertEquals(strlen($msgString), filesize($tmpfile));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server;
|
namespace LanguageServer\Tests\Server;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
|
@ -46,185 +47,187 @@ abstract class ServerTestCase extends TestCase
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$sourceIndex = new Index;
|
Loop::run(function () {
|
||||||
$dependenciesIndex = new DependenciesIndex;
|
$sourceIndex = new Index;
|
||||||
$projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex);
|
$dependenciesIndex = new DependenciesIndex;
|
||||||
$projectIndex->setComplete();
|
$projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex);
|
||||||
|
$projectIndex->setComplete();
|
||||||
|
|
||||||
$definitionResolver = new DefinitionResolver($projectIndex);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
||||||
$this->workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader);
|
$this->workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $this->documentLoader);
|
||||||
|
|
||||||
$globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php'));
|
$globalSymbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_symbols.php'));
|
||||||
$globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php'));
|
$globalReferencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/global_references.php'));
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../fixtures/symbols.php'));
|
||||||
$referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
$referencesUri = pathToUri(realpath(__DIR__ . '/../../fixtures/references.php'));
|
||||||
$useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php'));
|
$useUri = pathToUri(realpath(__DIR__ . '/../../fixtures/use.php'));
|
||||||
|
|
||||||
$this->documentLoader->load($symbolsUri)->wait();
|
yield from $this->documentLoader->load($symbolsUri);
|
||||||
$this->documentLoader->load($referencesUri)->wait();
|
yield from $this->documentLoader->load($referencesUri);
|
||||||
$this->documentLoader->load($globalSymbolsUri)->wait();
|
yield from $this->documentLoader->load($globalSymbolsUri);
|
||||||
$this->documentLoader->load($globalReferencesUri)->wait();
|
yield from $this->documentLoader->load($globalReferencesUri);
|
||||||
$this->documentLoader->load($useUri)->wait();
|
yield from $this->documentLoader->load($useUri);
|
||||||
|
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
$this->definitionLocations = [
|
$this->definitionLocations = [
|
||||||
|
|
||||||
// Global
|
// Global
|
||||||
'TEST_DEFINE_CONSTANT' => new Location($globalSymbolsUri, new Range(new Position(104, 0), new Position(104, 37))),
|
'TEST_DEFINE_CONSTANT' => new Location($globalSymbolsUri, new Range(new Position(104, 0), new Position(104, 37))),
|
||||||
'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
'TEST_CONST' => new Location($globalSymbolsUri, new Range(new Position(9, 6), new Position(9, 22))),
|
||||||
'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
'TestClass' => new Location($globalSymbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
||||||
'ChildClass' => new Location($globalSymbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
'ChildClass' => new Location($globalSymbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
||||||
'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
'TestTrait' => new Location($globalSymbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
||||||
'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
'TestInterface' => new Location($globalSymbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||||
'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
'TestClass::TEST_CLASS_CONST' => new Location($globalSymbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
||||||
'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
'TestClass::testProperty' => new Location($globalSymbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
||||||
'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
'TestClass::staticTestProperty' => new Location($globalSymbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
||||||
'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
'TestClass::staticTestMethod()' => new Location($globalSymbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
||||||
'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
'TestClass::testMethod()' => new Location($globalSymbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
||||||
'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
'test_function()' => new Location($globalSymbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
||||||
'UnusedClass' => new Location($globalSymbolsUri, new Range(new Position(111, 0), new Position(118, 1))),
|
'UnusedClass' => new Location($globalSymbolsUri, new Range(new Position(111, 0), new Position(118, 1))),
|
||||||
'UnusedClass::unusedProperty' => new Location($globalSymbolsUri, new Range(new Position(113,11), new Position(113, 26))),
|
'UnusedClass::unusedProperty' => new Location($globalSymbolsUri, new Range(new Position(113, 11), new Position(113, 26))),
|
||||||
'UnusedClass::unusedMethod' => new Location($globalSymbolsUri, new Range(new Position(115, 4), new Position(117, 5))),
|
'UnusedClass::unusedMethod' => new Location($globalSymbolsUri, new Range(new Position(115, 4), new Position(117, 5))),
|
||||||
'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
'whatever()' => new Location($globalReferencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
||||||
|
|
||||||
// Namespaced
|
// Namespaced
|
||||||
'TestNamespace' => new Location($symbolsUri, new Range(new Position( 2, 0), new Position( 2, 24))),
|
'TestNamespace' => new Location($symbolsUri, new Range(new Position(2, 0), new Position(2, 24))),
|
||||||
'SecondTestNamespace' => new Location($useUri, new Range(new Position( 2, 0), new Position( 2, 30))),
|
'SecondTestNamespace' => new Location($useUri, new Range(new Position(2, 0), new Position(2, 30))),
|
||||||
'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position( 9, 6), new Position( 9, 22))),
|
'TestNamespace\\TEST_CONST' => new Location($symbolsUri, new Range(new Position(9, 6), new Position(9, 22))),
|
||||||
'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
'TestNamespace\\TestClass' => new Location($symbolsUri, new Range(new Position(20, 0), new Position(61, 1))),
|
||||||
'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
'TestNamespace\\ChildClass' => new Location($symbolsUri, new Range(new Position(99, 0), new Position(99, 37))),
|
||||||
'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
'TestNamespace\\TestTrait' => new Location($symbolsUri, new Range(new Position(63, 0), new Position(66, 1))),
|
||||||
'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
'TestNamespace\\TestInterface' => new Location($symbolsUri, new Range(new Position(68, 0), new Position(71, 1))),
|
||||||
'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
'TestNamespace\\TestClass::TEST_CLASS_CONST' => new Location($symbolsUri, new Range(new Position(27, 10), new Position(27, 32))),
|
||||||
'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
'TestNamespace\\TestClass::testProperty' => new Location($symbolsUri, new Range(new Position(41, 11), new Position(41, 24))),
|
||||||
'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
'TestNamespace\\TestClass::staticTestProperty' => new Location($symbolsUri, new Range(new Position(34, 18), new Position(34, 37))),
|
||||||
'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
'TestNamespace\\TestClass::staticTestMethod()' => new Location($symbolsUri, new Range(new Position(46, 4), new Position(49, 5))),
|
||||||
'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
'TestNamespace\\TestClass::testMethod()' => new Location($symbolsUri, new Range(new Position(57, 4), new Position(60, 5))),
|
||||||
'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
'TestNamespace\\test_function()' => new Location($symbolsUri, new Range(new Position(78, 0), new Position(81, 1))),
|
||||||
'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
'TestNamespace\\whatever()' => new Location($referencesUri, new Range(new Position(21, 0), new Position(23, 1))),
|
||||||
'TestNamespace\\Example' => new Location($symbolsUri, new Range(new Position(101, 0), new Position(104, 1))),
|
'TestNamespace\\Example' => new Location($symbolsUri, new Range(new Position(101, 0), new Position(104, 1))),
|
||||||
'TestNamespace\\Example::__construct' => new Location($symbolsUri, new Range(new Position(102, 4), new Position(102, 36))),
|
'TestNamespace\\Example::__construct' => new Location($symbolsUri, new Range(new Position(102, 4), new Position(102, 36))),
|
||||||
'TestNamespace\\Example::__destruct' => new Location($symbolsUri, new Range(new Position(103, 4), new Position(103, 35))),
|
'TestNamespace\\Example::__destruct' => new Location($symbolsUri, new Range(new Position(103, 4), new Position(103, 35))),
|
||||||
'TestNamespace\\InnerNamespace' => new Location($symbolsUri, new Range(new Position(106, 0), new Position(106, 39))),
|
'TestNamespace\\InnerNamespace' => new Location($symbolsUri, new Range(new Position(106, 0), new Position(106, 39))),
|
||||||
'TestNamespace\\InnerNamespace\\InnerClass' => new Location($symbolsUri, new Range(new Position(108, 0), new Position(109, 1))),
|
'TestNamespace\\InnerNamespace\\InnerClass' => new Location($symbolsUri, new Range(new Position(108, 0), new Position(109, 1))),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->referenceLocations = [
|
$this->referenceLocations = [
|
||||||
|
|
||||||
// Namespaced
|
// Namespaced
|
||||||
'TestNamespace' => [
|
'TestNamespace' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function;
|
0 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40))), // use function TestNamespace\test_function;
|
||||||
1 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass;
|
1 => new Location($useUri, new Range(new Position(4, 4), new Position(4, 27))), // use TestNamespace\TestClass;
|
||||||
2 => new Location($useUri, new Range(new Position( 5, 4), new Position( 5, 18))) // use TestNamespace\{TestTrait, TestInterface};
|
2 => new Location($useUri, new Range(new Position(5, 4), new Position(5, 18))) // use TestNamespace\{TestTrait, TestInterface};
|
||||||
],
|
],
|
||||||
'TestNamespace\\TEST_CONST' => [
|
'TestNamespace\\TEST_CONST' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass' => [
|
'TestNamespace\\TestClass' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST;
|
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST;
|
||||||
1 => new Location($symbolsUri , new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
1 => new Location($symbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
||||||
2 => new Location($referencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
2 => new Location($referencesUri, new Range(new Position(4, 11), new Position(4, 20))), // $obj = new TestClass();
|
||||||
3 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
3 => new Location($referencesUri, new Range(new Position(7, 0), new Position(7, 9))), // TestClass::staticTestMethod();
|
||||||
4 => new Location($referencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
4 => new Location($referencesUri, new Range(new Position(8, 5), new Position(8, 14))), // echo TestClass::$staticTestProperty;
|
||||||
5 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
5 => new Location($referencesUri, new Range(new Position(9, 5), new Position(9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||||
6 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
6 => new Location($referencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||||
7 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
7 => new Location($referencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||||
8 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
8 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
9 => new Location($useUri, new Range(new Position( 4, 4), new Position( 4, 27))), // use TestNamespace\TestClass;
|
9 => new Location($useUri, new Range(new Position(4, 4), new Position(4, 27))), // use TestNamespace\TestClass;
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestChild' => [
|
'TestNamespace\\TestChild' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
0 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestInterface' => [
|
'TestNamespace\\TestInterface' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
0 => new Location($symbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||||
1 => new Location($symbolsUri, new Range(new Position(57, 48), new Position(57, 61))), // public function testMethod($testParameter): TestInterface
|
1 => new Location($symbolsUri, new Range(new Position(57, 48), new Position(57, 61))), // public function testMethod($testParameter): TestInterface
|
||||||
2 => new Location($referencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
2 => new Location($referencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::TEST_CLASS_CONST' => [
|
'TestNamespace\\TestClass::TEST_CLASS_CONST' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
0 => new Location($symbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
||||||
1 => new Location($referencesUri, new Range(new Position( 9, 5), new Position( 9, 32)))
|
1 => new Location($referencesUri, new Range(new Position(9, 5), new Position(9, 32)))
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::testProperty' => [
|
'TestNamespace\\TestClass::testProperty' => [
|
||||||
0 => new Location($symbolsUri, new Range(new Position(59, 8), new Position(59, 27))), // $this->testProperty = $testParameter;
|
0 => new Location($symbolsUri, new Range(new Position(59, 8), new Position(59, 27))), // $this->testProperty = $testParameter;
|
||||||
1 => new Location($referencesUri, new Range(new Position( 6, 5), new Position( 6, 23))), // echo $obj->testProperty;
|
1 => new Location($referencesUri, new Range(new Position(6, 5), new Position(6, 23))), // echo $obj->testProperty;
|
||||||
2 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 18))), // $obj->testProperty->testMethod();
|
2 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 18))), // $obj->testProperty->testMethod();
|
||||||
3 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
3 => new Location($referencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::staticTestProperty' => [
|
'TestNamespace\\TestClass::staticTestProperty' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
0 => new Location($referencesUri, new Range(new Position(8, 16), new Position(8, 35))), // echo TestClass::$staticTestProperty;
|
||||||
1 => new Location($referencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
1 => new Location($referencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::staticTestMethod()' => [
|
'TestNamespace\\TestClass::staticTestMethod()' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position( 7, 0), new Position( 7, 27)))
|
0 => new Location($referencesUri, new Range(new Position(7, 0), new Position(7, 27)))
|
||||||
],
|
],
|
||||||
'TestNamespace\\TestClass::testMethod()' => [
|
'TestNamespace\\TestClass::testMethod()' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod();
|
0 => new Location($referencesUri, new Range(new Position(5, 0), new Position(5, 16))), // $obj->testMethod();
|
||||||
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
1 => new Location($referencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
||||||
2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
2 => new Location($referencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
||||||
],
|
],
|
||||||
'TestNamespace\\test_function()' => [
|
'TestNamespace\\test_function()' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
0 => new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
1 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
1 => new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
],
|
],
|
||||||
|
|
||||||
// Global
|
// Global
|
||||||
'TEST_DEFINE_CONSTANT' => [
|
'TEST_DEFINE_CONSTANT' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(106, 6), new Position(106, 26)))
|
0 => new Location($globalSymbolsUri, new Range(new Position(106, 6), new Position(106, 26)))
|
||||||
],
|
],
|
||||||
'TEST_CONST' => [
|
'TEST_CONST' => [
|
||||||
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))),
|
0 => new Location($referencesUri, new Range(new Position(29, 5), new Position(29, 15))),
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
1 => new Location($globalReferencesUri, new Range(new Position(29, 5), new Position(29, 15)))
|
||||||
],
|
],
|
||||||
'TestClass' => [
|
'TestClass' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST;
|
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 17))), // echo self::TEST_CLASS_CONST;
|
||||||
1 => new Location($globalSymbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
1 => new Location($globalSymbolsUri, new Range(new Position(99, 25), new Position(99, 34))), // class ChildClass extends TestClass {}
|
||||||
2 => new Location($globalReferencesUri, new Range(new Position( 4, 11), new Position( 4, 20))), // $obj = new TestClass();
|
2 => new Location($globalReferencesUri, new Range(new Position(4, 11), new Position(4, 20))), // $obj = new TestClass();
|
||||||
3 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 9))), // TestClass::staticTestMethod();
|
3 => new Location($globalReferencesUri, new Range(new Position(7, 0), new Position(7, 9))), // TestClass::staticTestMethod();
|
||||||
4 => new Location($globalReferencesUri, new Range(new Position( 8, 5), new Position( 8, 14))), // echo TestClass::$staticTestProperty;
|
4 => new Location($globalReferencesUri, new Range(new Position(8, 5), new Position(8, 14))), // echo TestClass::$staticTestProperty;
|
||||||
5 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 14))), // TestClass::TEST_CLASS_CONST;
|
5 => new Location($globalReferencesUri, new Range(new Position(9, 5), new Position(9, 14))), // TestClass::TEST_CLASS_CONST;
|
||||||
6 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
6 => new Location($globalReferencesUri, new Range(new Position(21, 18), new Position(21, 27))), // function whatever(TestClass $param)
|
||||||
7 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
7 => new Location($globalReferencesUri, new Range(new Position(21, 37), new Position(21, 46))), // function whatever(TestClass $param): TestClass
|
||||||
8 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
8 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 9))), // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
],
|
],
|
||||||
'TestChild' => [
|
'TestChild' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
0 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 25))), // echo $child->testProperty;
|
||||||
],
|
],
|
||||||
'TestInterface' => [
|
'TestInterface' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
0 => new Location($globalSymbolsUri, new Range(new Position(20, 27), new Position(20, 40))), // class TestClass implements TestInterface
|
||||||
1 => new Location($globalSymbolsUri, new Range(new Position(57, 49), new Position(57, 61))), // public function testMethod($testParameter) : TestInterface
|
1 => new Location($globalSymbolsUri, new Range(new Position(57, 49), new Position(57, 61))), // public function testMethod($testParameter) : TestInterface
|
||||||
2 => new Location($globalReferencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
2 => new Location($globalReferencesUri, new Range(new Position(33, 20), new Position(33, 33))) // if ($abc instanceof TestInterface)
|
||||||
],
|
],
|
||||||
'TestClass::TEST_CLASS_CONST' => [
|
'TestClass::TEST_CLASS_CONST' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
0 => new Location($globalSymbolsUri, new Range(new Position(48, 13), new Position(48, 35))), // echo self::TEST_CLASS_CONSTANT
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position( 9, 5), new Position( 9, 32)))
|
1 => new Location($globalReferencesUri, new Range(new Position(9, 5), new Position(9, 32)))
|
||||||
],
|
],
|
||||||
'TestClass::testProperty' => [
|
'TestClass::testProperty' => [
|
||||||
0 => new Location($globalSymbolsUri, new Range(new Position(59, 8), new Position(59, 27))), // $this->testProperty = $testParameter;
|
0 => new Location($globalSymbolsUri, new Range(new Position(59, 8), new Position(59, 27))), // $this->testProperty = $testParameter;
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position( 6, 5), new Position( 6, 23))), // echo $obj->testProperty;
|
1 => new Location($globalReferencesUri, new Range(new Position(6, 5), new Position(6, 23))), // echo $obj->testProperty;
|
||||||
2 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 18))), // $obj->testProperty->testMethod();
|
2 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 18))), // $obj->testProperty->testMethod();
|
||||||
3 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
3 => new Location($globalReferencesUri, new Range(new Position(39, 0), new Position(39, 49))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
],
|
],
|
||||||
'TestClass::staticTestProperty' => [
|
'TestClass::staticTestProperty' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position( 8, 16), new Position( 8, 35))), // echo TestClass::$staticTestProperty;
|
0 => new Location($globalReferencesUri, new Range(new Position(8, 16), new Position(8, 35))), // echo TestClass::$staticTestProperty;
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
1 => new Location($globalReferencesUri, new Range(new Position(39, 11), new Position(39, 30))) // TestClass::$staticTestProperty[123]->testProperty;
|
||||||
],
|
],
|
||||||
'TestClass::staticTestMethod()' => [
|
'TestClass::staticTestMethod()' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position( 7, 0), new Position( 7, 27)))
|
0 => new Location($globalReferencesUri, new Range(new Position(7, 0), new Position(7, 27)))
|
||||||
],
|
],
|
||||||
'TestClass::testMethod()' => [
|
'TestClass::testMethod()' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position( 5, 0), new Position( 5, 16))), // $obj->testMethod();
|
0 => new Location($globalReferencesUri, new Range(new Position(5, 0), new Position(5, 16))), // $obj->testMethod();
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
1 => new Location($globalReferencesUri, new Range(new Position(38, 0), new Position(38, 30))), // $obj->testProperty->testMethod();
|
||||||
2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
2 => new Location($globalReferencesUri, new Range(new Position(42, 5), new Position(42, 23))) // $child->testMethod();
|
||||||
],
|
],
|
||||||
'test_function()' => [
|
'test_function()' => [
|
||||||
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
0 => new Location($globalReferencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
1 => new Location($globalReferencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
1 => new Location($globalReferencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
// @codingStandardsIgnoreEnd
|
// @codingStandardsIgnoreEnd
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDefinitionLocation(string $fqn): Location
|
protected function getDefinitionLocation(string $fqn): Location
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
|
@ -29,34 +30,40 @@ class GlobalFallbackTest extends ServerTestCase
|
||||||
|
|
||||||
public function testClassDoesNotFallback()
|
public function testClassDoesNotFallback()
|
||||||
{
|
{
|
||||||
// $obj = new TestClass();
|
Loop::run(function () {
|
||||||
// Get definition for TestClass should not fall back to global
|
// $obj = new TestClass();
|
||||||
$result = $this->textDocument->definition(
|
// Get definition for TestClass should not fall back to global
|
||||||
new TextDocumentIdentifier('global_fallback'),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(9, 16)
|
new TextDocumentIdentifier('global_fallback'),
|
||||||
)->wait();
|
new Position(9, 16)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFallsBackForConstants()
|
public function testFallsBackForConstants()
|
||||||
{
|
{
|
||||||
// echo TEST_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TEST_CONST
|
// echo TEST_CONST;
|
||||||
$result = $this->textDocument->definition(
|
// Get definition for TEST_CONST
|
||||||
new TextDocumentIdentifier('global_fallback'),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(6, 10)
|
new TextDocumentIdentifier('global_fallback'),
|
||||||
)->wait();
|
new Position(6, 10)
|
||||||
$this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result);
|
);
|
||||||
|
$this->assertEquals(new Location('global_symbols', new Range(new Position(9, 6), new Position(9, 22))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFallsBackForFunctions()
|
public function testFallsBackForFunctions()
|
||||||
{
|
{
|
||||||
// test_function();
|
Loop::run(function () {
|
||||||
// Get definition for test_function
|
// test_function();
|
||||||
$result = $this->textDocument->definition(
|
// Get definition for test_function
|
||||||
new TextDocumentIdentifier('global_fallback'),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(5, 6)
|
new TextDocumentIdentifier('global_fallback'),
|
||||||
)->wait();
|
new Position(5, 6)
|
||||||
$this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result);
|
);
|
||||||
|
$this->assertEquals(new Location('global_symbols', new Range(new Position(78, 0), new Position(81, 1))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServerProtocol\{TextDocumentIdentifier, Position, Location, Range};
|
use LanguageServerProtocol\{TextDocumentIdentifier, Position, Location, Range};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
@ -11,333 +12,389 @@ class GlobalTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testDefinitionFileBeginning()
|
public function testDefinitionFileBeginning()
|
||||||
{
|
{
|
||||||
// |<?php
|
Loop::run(function () {
|
||||||
$result = $this->textDocument->definition(
|
// |<?php
|
||||||
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(0, 0)
|
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
|
||||||
)->wait();
|
new Position(0, 0)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionEmptyResult()
|
public function testDefinitionEmptyResult()
|
||||||
{
|
{
|
||||||
// namespace keyword
|
Loop::run(function () {
|
||||||
$result = $this->textDocument->definition(
|
// namespace keyword
|
||||||
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(1, 0)
|
new TextDocumentIdentifier(pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'))),
|
||||||
)->wait();
|
new Position(1, 0)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForSelfKeyword()
|
public function testDefinitionForSelfKeyword()
|
||||||
{
|
{
|
||||||
// echo self::TEST_CLASS_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for self
|
// echo self::TEST_CLASS_CONST;
|
||||||
$reference = $this->getReferenceLocations('TestClass')[0];
|
// Get definition for self
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassLike()
|
public function testDefinitionForClassLike()
|
||||||
{
|
{
|
||||||
// $obj = new TestClass();
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// $obj = new TestClass();
|
||||||
$reference = $this->getReferenceLocations('TestClass')[1];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassOnStaticMethodCall()
|
public function testDefinitionForClassOnStaticMethodCall()
|
||||||
{
|
{
|
||||||
// TestClass::staticTestMethod();
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// TestClass::staticTestMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass')[2];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[2];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassOnStaticPropertyFetch()
|
public function testDefinitionForClassOnStaticPropertyFetch()
|
||||||
{
|
{
|
||||||
// echo TestClass::$staticTestProperty;
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// echo TestClass::$staticTestProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass')[3];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[3];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassOnConstFetch()
|
public function testDefinitionForClassOnConstFetch()
|
||||||
{
|
{
|
||||||
// TestClass::TEST_CLASS_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// TestClass::TEST_CLASS_CONST;
|
||||||
$reference = $this->getReferenceLocations('TestClass')[4];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[4];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForImplements()
|
public function testDefinitionForImplements()
|
||||||
{
|
{
|
||||||
// class TestClass implements TestInterface
|
Loop::run(function () {
|
||||||
// Get definition for TestInterface
|
// class TestClass implements TestInterface
|
||||||
$reference = $this->getReferenceLocations('TestInterface')[0];
|
// Get definition for TestInterface
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestInterface')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassConstants()
|
public function testDefinitionForClassConstants()
|
||||||
{
|
{
|
||||||
// echo TestClass::TEST_CLASS_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TEST_CLASS_CONST
|
// echo TestClass::TEST_CLASS_CONST;
|
||||||
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[1];
|
// Get definition for TEST_CLASS_CONST
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassConstantsOnSelf()
|
public function testDefinitionForClassConstantsOnSelf()
|
||||||
{
|
{
|
||||||
// echo self::TEST_CLASS_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TEST_CLASS_CONST
|
// echo self::TEST_CLASS_CONST;
|
||||||
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
|
// Get definition for TEST_CLASS_CONST
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForConstants()
|
public function testDefinitionForConstants()
|
||||||
{
|
{
|
||||||
// echo TEST_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TEST_CONST
|
// echo TEST_CONST;
|
||||||
$reference = $this->getReferenceLocations('TEST_CONST')[1];
|
// Get definition for TEST_CONST
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TEST_CONST')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForStaticMethods()
|
public function testDefinitionForStaticMethods()
|
||||||
{
|
{
|
||||||
// TestClass::staticTestMethod();
|
Loop::run(function () {
|
||||||
// Get definition for staticTestMethod
|
// TestClass::staticTestMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
|
// Get definition for staticTestMethod
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForStaticProperties()
|
public function testDefinitionForStaticProperties()
|
||||||
{
|
{
|
||||||
// echo TestClass::$staticTestProperty;
|
Loop::run(function () {
|
||||||
// Get definition for staticTestProperty
|
// echo TestClass::$staticTestProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
|
// Get definition for staticTestProperty
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::staticTestProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForMethods()
|
public function testDefinitionForMethods()
|
||||||
{
|
{
|
||||||
// $obj->testMethod();
|
Loop::run(function () {
|
||||||
// Get definition for testMethod
|
// $obj->testMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
|
// Get definition for testMethod
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForMethodOnChildClass()
|
public function testDefinitionForMethodOnChildClass()
|
||||||
{
|
{
|
||||||
// $child->testMethod();
|
Loop::run(function () {
|
||||||
// Get definition for testMethod
|
// $child->testMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::testMethod()')[2];
|
// Get definition for testMethod
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testMethod()')[2];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForProperties()
|
public function testDefinitionForProperties()
|
||||||
{
|
{
|
||||||
// echo $obj->testProperty;
|
Loop::run(function () {
|
||||||
// Get definition for testProperty
|
// echo $obj->testProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass::testProperty')[1];
|
// Get definition for testProperty
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testProperty')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForPropertiesOnThis()
|
public function testDefinitionForPropertiesOnThis()
|
||||||
{
|
{
|
||||||
// $this->testProperty = $testParameter;
|
Loop::run(function () {
|
||||||
// Get definition for testProperty
|
// $this->testProperty = $testParameter;
|
||||||
$reference = $this->getReferenceLocations('TestClass::testProperty')[0];
|
// Get definition for testProperty
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testProperty')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForVariables()
|
public function testDefinitionForVariables()
|
||||||
{
|
{
|
||||||
// echo $var;
|
Loop::run(function () {
|
||||||
// Get definition for $var
|
// echo $var;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get definition for $var
|
||||||
$result = $this->textDocument->definition(
|
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
new TextDocumentIdentifier($uri),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(13, 7)
|
new TextDocumentIdentifier($uri),
|
||||||
)->wait();
|
new Position(13, 7)
|
||||||
$this->assertEquals(new Location($uri, new Range(new Position(12, 0), new Position(12, 10))), $result);
|
);
|
||||||
|
$this->assertEquals(new Location($uri, new Range(new Position(12, 0), new Position(12, 10))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForParamTypeHints()
|
public function testDefinitionForParamTypeHints()
|
||||||
{
|
{
|
||||||
// function whatever(TestClass $param) {
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// function whatever(TestClass $param) {
|
||||||
$reference = $this->getReferenceLocations('TestClass')[5];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[5];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForReturnTypeHints()
|
public function testDefinitionForReturnTypeHints()
|
||||||
{
|
{
|
||||||
// function whatever(TestClass $param): TestClass {
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// function whatever(TestClass $param): TestClass {
|
||||||
$reference = $this->getReferenceLocations('TestClass')[6];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[6];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForMethodReturnTypeHints()
|
public function testDefinitionForMethodReturnTypeHints()
|
||||||
{
|
{
|
||||||
// public function testMethod($testParameter): TestInterface
|
Loop::run(function () {
|
||||||
// Get definition for TestInterface
|
// public function testMethod($testParameter): TestInterface
|
||||||
$reference = $this->getReferenceLocations('TestInterface')[1];
|
// Get definition for TestInterface
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestInterface')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForParams()
|
public function testDefinitionForParams()
|
||||||
{
|
{
|
||||||
// echo $param;
|
Loop::run(function () {
|
||||||
// Get definition for $param
|
// echo $param;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get definition for $param
|
||||||
$result = $this->textDocument->definition(
|
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
new TextDocumentIdentifier($uri),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(22, 13)
|
new TextDocumentIdentifier($uri),
|
||||||
)->wait();
|
new Position(22, 13)
|
||||||
$this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result);
|
);
|
||||||
|
$this->assertEquals(new Location($uri, new Range(new Position(21, 18), new Position(21, 34))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForUsedVariables()
|
public function testDefinitionForUsedVariables()
|
||||||
{
|
{
|
||||||
// echo $var;
|
Loop::run(function () {
|
||||||
// Get definition for $var
|
// echo $var;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get definition for $var
|
||||||
$result = $this->textDocument->definition(
|
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
new TextDocumentIdentifier($uri),
|
$result = yield $this->textDocument->definition(
|
||||||
new Position(26, 11)
|
new TextDocumentIdentifier($uri),
|
||||||
)->wait();
|
new Position(26, 11)
|
||||||
$this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result);
|
);
|
||||||
|
$this->assertEquals(new Location($uri, new Range(new Position(25, 22), new Position(25, 26))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForFunctions()
|
public function testDefinitionForFunctions()
|
||||||
{
|
{
|
||||||
// test_function();
|
Loop::run(function () {
|
||||||
// Get definition for test_function
|
// test_function();
|
||||||
$reference = $this->getReferenceLocations('test_function()')[0];
|
// Get definition for test_function
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('test_function()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForUseFunctions()
|
public function testDefinitionForUseFunctions()
|
||||||
{
|
{
|
||||||
// use function test_function;
|
Loop::run(function () {
|
||||||
// Get definition for test_function
|
// use function test_function;
|
||||||
$reference = $this->getReferenceLocations('test_function()')[1];
|
// Get definition for test_function
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('test_function()')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('test_function()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForInstanceOf()
|
public function testDefinitionForInstanceOf()
|
||||||
{
|
{
|
||||||
// if ($abc instanceof TestInterface) {
|
Loop::run(function () {
|
||||||
// Get definition for TestInterface
|
// if ($abc instanceof TestInterface) {
|
||||||
$reference = $this->getReferenceLocations('TestInterface')[2];
|
// Get definition for TestInterface
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestInterface')[2];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestInterface'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForNestedMethodCall()
|
public function testDefinitionForNestedMethodCall()
|
||||||
{
|
{
|
||||||
// $obj->testProperty->testMethod();
|
Loop::run(function () {
|
||||||
// Get definition for testMethod
|
// $obj->testProperty->testMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::testMethod()')[1];
|
// Get definition for testMethod
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testMethod()')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForPropertyFetchOnArrayDimFetch()
|
public function testDefinitionForPropertyFetchOnArrayDimFetch()
|
||||||
{
|
{
|
||||||
// TestClass::$staticTestProperty[123]->testProperty;
|
Loop::run(function () {
|
||||||
// Get definition for testProperty
|
// TestClass::$staticTestProperty[123]->testProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass::testProperty')[3];
|
// Get definition for testProperty
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass::testProperty')[3];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass::testProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
namespace LanguageServer\Tests\Server\TextDocument\Definition;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServerProtocol\{TextDocumentIdentifier, Location};
|
use LanguageServerProtocol\{TextDocumentIdentifier, Location};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
|
||||||
|
@ -20,37 +21,43 @@ class NamespacedTest extends GlobalTest
|
||||||
|
|
||||||
public function testDefinitionForConstants()
|
public function testDefinitionForConstants()
|
||||||
{
|
{
|
||||||
// echo TEST_CONST;
|
Loop::run(function () {
|
||||||
// Get definition for TEST_CONST
|
// echo TEST_CONST;
|
||||||
$reference = $this->getReferenceLocations('TEST_CONST')[0];
|
// Get definition for TEST_CONST
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TEST_CONST')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TEST_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassLikeUseStatement()
|
public function testDefinitionForClassLikeUseStatement()
|
||||||
{
|
{
|
||||||
// use TestNamespace\TestClass;
|
Loop::run(function () {
|
||||||
// Get definition for TestClass
|
// use TestNamespace\TestClass;
|
||||||
$reference = $this->getReferenceLocations('TestClass')[7];
|
// Get definition for TestClass
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[7];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefinitionForClassLikeGroupUseStatement()
|
public function testDefinitionForClassLikeGroupUseStatement()
|
||||||
{
|
{
|
||||||
// use TestNamespace\{TestTrait, TestInterface};
|
Loop::run(function () {
|
||||||
// Get definition for TestInterface
|
// use TestNamespace\{TestTrait, TestInterface};
|
||||||
$reference = $this->getReferenceLocations('TestClass')[1];
|
// Get definition for TestInterface
|
||||||
$result = $this->textDocument->definition(
|
$reference = $this->getReferenceLocations('TestClass')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->definition(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getDefinitionLocation('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
|
@ -21,21 +22,23 @@ class DidChangeTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
Loop::run(function () {
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$definitionResolver = new DefinitionResolver($projectIndex);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
||||||
|
|
||||||
$identifier = new VersionedTextDocumentIdentifier('whatever');
|
$identifier = new VersionedTextDocumentIdentifier('whatever');
|
||||||
$changeEvent = new TextDocumentContentChangeEvent();
|
$changeEvent = new TextDocumentContentChangeEvent();
|
||||||
$changeEvent->range = new Range(new Position(0, 0), new Position(9999, 9999));
|
$changeEvent->range = new Range(new Position(0, 0), new Position(9999, 9999));
|
||||||
$changeEvent->rangeLength = 9999;
|
$changeEvent->rangeLength = 9999;
|
||||||
$changeEvent->text = "<?php\necho 'Goodbye, World'\n";
|
$changeEvent->text = "<?php\necho 'Goodbye, World'\n";
|
||||||
|
|
||||||
$textDocument->didChange($identifier, [$changeEvent]);
|
yield $textDocument->didChange($identifier, [$changeEvent]);
|
||||||
|
|
||||||
$this->assertEquals("<?php\necho 'Goodbye, World'\n", $phpDocument->getContent());
|
$this->assertEquals("<?php\necho 'Goodbye, World'\n", $phpDocument->getContent());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
|
@ -16,22 +17,24 @@ class DidCloseTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
Loop::run(function () {
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$definitionResolver = new DefinitionResolver($projectIndex);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
$textDocument = new Server\TextDocument($loader, $definitionResolver, $client, $projectIndex);
|
||||||
|
$phpDocument = $loader->open('whatever', "<?php\necho 'Hello, World'\n");
|
||||||
|
|
||||||
$textDocumentItem = new TextDocumentItem();
|
$textDocumentItem = new TextDocumentItem();
|
||||||
$textDocumentItem->uri = 'whatever';
|
$textDocumentItem->uri = 'whatever';
|
||||||
$textDocumentItem->languageId = 'php';
|
$textDocumentItem->languageId = 'php';
|
||||||
$textDocumentItem->version = 1;
|
$textDocumentItem->version = 1;
|
||||||
$textDocumentItem->text = 'hello world';
|
$textDocumentItem->text = 'hello world';
|
||||||
$textDocument->didOpen($textDocumentItem);
|
$textDocument->didOpen($textDocumentItem);
|
||||||
|
|
||||||
$textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri));
|
$textDocument->didClose(new TextDocumentIdentifier($textDocumentItem->uri));
|
||||||
|
|
||||||
$this->assertFalse($loader->isOpen($textDocumentItem->uri));
|
$this->assertFalse($loader->isOpen($textDocumentItem->uri));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{Server, LanguageClient, Project};
|
use LanguageServer\{Server, LanguageClient, Project};
|
||||||
|
@ -13,29 +14,31 @@ class DocumentSymbolTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
// Request symbols
|
Loop::run(function () {
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
|
// Request symbols
|
||||||
$result = $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri))->wait();
|
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/symbols.php'));
|
||||||
// @codingStandardsIgnoreStart
|
$result = yield $this->textDocument->documentSymbol(new TextDocumentIdentifier($uri));
|
||||||
$this->assertEquals([
|
// @codingStandardsIgnoreStart
|
||||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''),
|
$this->assertEquals([
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace'), ''),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
||||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||||
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'),
|
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'),
|
||||||
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
||||||
], $result);
|
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
||||||
// @codingStandardsIgnoreEnd
|
], $result);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServer\{Server, LanguageClient, Project};
|
use LanguageServer\{Server, LanguageClient, Project};
|
||||||
|
@ -13,206 +14,241 @@ class HoverTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testHoverForClassLike()
|
public function testHoverForClassLike()
|
||||||
{
|
{
|
||||||
// $obj = new TestClass();
|
Loop::run(function () {
|
||||||
// Get hover for TestClass
|
// $obj = new TestClass();
|
||||||
$reference = $this->getReferenceLocations('TestClass')[1];
|
// Get hover for TestClass
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass')[1];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
$this->assertEquals(new Hover([
|
||||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||||
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
||||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||||
], $reference->range), $result);
|
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForClassLikeDefinition()
|
public function testHoverForClassLikeDefinition()
|
||||||
{
|
{
|
||||||
// class TestClass implements TestInterface
|
Loop::run(function () {
|
||||||
// Get hover for TestClass
|
// class TestClass implements TestInterface
|
||||||
$definition = $this->getDefinitionLocation('TestClass');
|
// Get hover for TestClass
|
||||||
$result = $this->textDocument->hover(
|
$definition = $this->getDefinitionLocation('TestClass');
|
||||||
new TextDocumentIdentifier($definition->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
$this->assertEquals(new Hover([
|
||||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||||
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
||||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||||
], $definition->range), $result);
|
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
||||||
|
], $definition->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForMethod()
|
public function testHoverForMethod()
|
||||||
{
|
{
|
||||||
// $obj->testMethod();
|
Loop::run(function () {
|
||||||
// Get hover for testMethod
|
// $obj->testMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
|
// Get hover for testMethod
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass::testMethod()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter): TestInterface"),
|
$this->assertEquals(new Hover([
|
||||||
'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.'
|
new MarkedString('php', "<?php\npublic function testMethod(\$testParameter): TestInterface"),
|
||||||
], $reference->range), $result);
|
'Non culpa nostrud mollit esse sunt laboris in irure ullamco cupidatat amet.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForProperty()
|
public function testHoverForProperty()
|
||||||
{
|
{
|
||||||
// echo $obj->testProperty;
|
Loop::run(function () {
|
||||||
// Get hover for testProperty
|
// echo $obj->testProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass::testProperty')[0];
|
// Get hover for testProperty
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass::testProperty')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\npublic \$testProperty;"),
|
$this->assertEquals(new Hover([
|
||||||
'Reprehenderit magna velit mollit ipsum do.'
|
new MarkedString('php', "<?php\npublic \$testProperty;"),
|
||||||
], $reference->range), $result);
|
'Reprehenderit magna velit mollit ipsum do.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForStaticMethod()
|
public function testHoverForStaticMethod()
|
||||||
{
|
{
|
||||||
// TestClass::staticTestMethod();
|
Loop::run(function () {
|
||||||
// Get hover for staticTestMethod
|
// TestClass::staticTestMethod();
|
||||||
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
|
// Get hover for staticTestMethod
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass::staticTestMethod()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\npublic static function staticTestMethod()"),
|
$this->assertEquals(new Hover([
|
||||||
'Do magna consequat veniam minim proident eiusmod incididunt aute proident.'
|
new MarkedString('php', "<?php\npublic static function staticTestMethod()"),
|
||||||
], $reference->range), $result);
|
'Do magna consequat veniam minim proident eiusmod incididunt aute proident.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForStaticProperty()
|
public function testHoverForStaticProperty()
|
||||||
{
|
{
|
||||||
// echo TestClass::staticTestProperty;
|
Loop::run(function () {
|
||||||
// Get hover for staticTestProperty
|
// echo TestClass::staticTestProperty;
|
||||||
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
|
// Get hover for staticTestProperty
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass::staticTestProperty')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\npublic static \$staticTestProperty;"),
|
$this->assertEquals(new Hover([
|
||||||
'Lorem excepteur officia sit anim velit veniam enim.'
|
new MarkedString('php', "<?php\npublic static \$staticTestProperty;"),
|
||||||
], $reference->range), $result);
|
'Lorem excepteur officia sit anim velit veniam enim.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForClassConstant()
|
public function testHoverForClassConstant()
|
||||||
{
|
{
|
||||||
// echo TestClass::TEST_CLASS_CONST;
|
Loop::run(function () {
|
||||||
// Get hover for TEST_CLASS_CONST
|
// echo TestClass::TEST_CLASS_CONST;
|
||||||
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
|
// Get hover for TEST_CLASS_CONST
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TestClass::TEST_CLASS_CONST')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\nconst TEST_CLASS_CONST = 123;"),
|
$this->assertEquals(new Hover([
|
||||||
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
|
new MarkedString('php', "<?php\nconst TEST_CLASS_CONST = 123;"),
|
||||||
], $reference->range), $result);
|
'Anim labore veniam consectetur laboris minim quis aute aute esse nulla ad.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForFunction()
|
public function testHoverForFunction()
|
||||||
{
|
{
|
||||||
// test_function();
|
Loop::run(function () {
|
||||||
// Get hover for test_function
|
// test_function();
|
||||||
$reference = $this->getReferenceLocations('test_function()')[0];
|
// Get hover for test_function
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('test_function()')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\nfunction test_function()"),
|
$this->assertEquals(new Hover([
|
||||||
'Officia aliquip adipisicing et nulla et laboris dolore labore.'
|
new MarkedString('php', "<?php\nfunction test_function()"),
|
||||||
], $reference->range), $result);
|
'Officia aliquip adipisicing et nulla et laboris dolore labore.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForConstant()
|
public function testHoverForConstant()
|
||||||
{
|
{
|
||||||
// echo TEST_CONST;
|
Loop::run(function () {
|
||||||
// Get hover for TEST_CONST
|
// echo TEST_CONST;
|
||||||
$reference = $this->getReferenceLocations('TEST_CONST')[0];
|
// Get hover for TEST_CONST
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TEST_CONST')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
$this->assertEquals(new Hover([
|
);
|
||||||
new MarkedString('php', "<?php\nconst TEST_CONST = 123;"),
|
$this->assertEquals(new Hover([
|
||||||
'Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit.'
|
new MarkedString('php', "<?php\nconst TEST_CONST = 123;"),
|
||||||
], $reference->range), $result);
|
'Esse commodo excepteur pariatur Lorem est aute incididunt reprehenderit.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForGlobalConstant()
|
public function testHoverForGlobalConstant()
|
||||||
{
|
{
|
||||||
// print TEST_DEFINE_CONSTANT ? 'true' : 'false';
|
Loop::run(function () {
|
||||||
// Get hover for TEST_DEFINE_CONSTANT
|
// print TEST_DEFINE_CONSTANT ? 'true' : 'false';
|
||||||
$reference = $this->getReferenceLocations('TEST_DEFINE_CONSTANT')[0];
|
// Get hover for TEST_DEFINE_CONSTANT
|
||||||
$result = $this->textDocument->hover(
|
$reference = $this->getReferenceLocations('TEST_DEFINE_CONSTANT')[0];
|
||||||
new TextDocumentIdentifier($reference->uri),
|
$result = yield $this->textDocument->hover(
|
||||||
$reference->range->end
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->end
|
||||||
// TODO - should pretty print with fqns, like \define, \false. Not yet supported by tolerant-php-parser
|
);
|
||||||
$this->assertEquals(new Hover([
|
// TODO - should pretty print with fqns, like \define, \false. Not yet supported by tolerant-php-parser
|
||||||
new MarkedString('php', "<?php\ndefine('TEST_DEFINE_CONSTANT', false)"),
|
$this->assertEquals(new Hover([
|
||||||
'Lorem ipsum dolor sit amet, consectetur.'
|
new MarkedString('php', "<?php\ndefine('TEST_DEFINE_CONSTANT', false)"),
|
||||||
], $reference->range), $result);
|
'Lorem ipsum dolor sit amet, consectetur.'
|
||||||
|
], $reference->range), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForVariable()
|
public function testHoverForVariable()
|
||||||
{
|
{
|
||||||
// echo $var;
|
Loop::run(function () {
|
||||||
// Get hover for $var
|
// echo $var;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
// Get hover for $var
|
||||||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(13, 7))->wait();
|
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||||
$this->assertEquals(new Hover(
|
$result = yield $this->textDocument->hover(
|
||||||
[new MarkedString('php', "<?php\n\$var = 123")],
|
new TextDocumentIdentifier($uri),
|
||||||
new Range(new Position(13, 5), new Position(13, 9))
|
new Position(13, 7)
|
||||||
), $result);
|
);
|
||||||
|
$this->assertEquals(new Hover(
|
||||||
|
[new MarkedString('php', "<?php\n\$var = 123")],
|
||||||
|
new Range(new Position(13, 5), new Position(13, 9))
|
||||||
|
), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForParam()
|
public function testHoverForParam()
|
||||||
{
|
{
|
||||||
// echo $param;
|
Loop::run(function () {
|
||||||
// Get hover for $param
|
// echo $param;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
// Get hover for $param
|
||||||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(22, 11))->wait();
|
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||||
$this->assertEquals(new Hover(
|
$result = yield $this->textDocument->hover(
|
||||||
[
|
new TextDocumentIdentifier($uri),
|
||||||
new MarkedString('php', "<?php\nTestClass \$param"),
|
new Position(22, 11)
|
||||||
'Adipisicing non non cillum sint incididunt cillum enim mollit.'
|
);
|
||||||
],
|
$this->assertEquals(new Hover(
|
||||||
new Range(new Position(22, 9), new Position(22, 15))
|
[
|
||||||
), $result);
|
new MarkedString('php', "<?php\nTestClass \$param"),
|
||||||
|
'Adipisicing non non cillum sint incididunt cillum enim mollit.'
|
||||||
|
],
|
||||||
|
new Range(new Position(22, 9), new Position(22, 15))
|
||||||
|
), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHoverForThis()
|
public function testHoverForThis()
|
||||||
{
|
{
|
||||||
// $this;
|
Loop::run(function () {
|
||||||
// Get hover for $this
|
// $this;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/global_symbols.php'));
|
// Get hover for $this
|
||||||
$result = $this->textDocument->hover(new TextDocumentIdentifier($uri), new Position(59, 11))->wait();
|
$uri = pathToUri(realpath(__DIR__ . '/../../../fixtures/global_symbols.php'));
|
||||||
$this->assertEquals(new Hover([
|
$result = yield $this->textDocument->hover(
|
||||||
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
new TextDocumentIdentifier($uri),
|
||||||
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
new Position(59, 11)
|
||||||
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
);
|
||||||
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
$this->assertEquals(new Hover([
|
||||||
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
new MarkedString('php', "<?php\nclass TestClass implements TestInterface"),
|
||||||
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
'Pariatur ut laborum tempor voluptate consequat ea deserunt.' . "\n\n" .
|
||||||
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
'Deserunt enim minim sunt sint ea nisi. Deserunt excepteur tempor id nostrud' . "\n" .
|
||||||
], new Range(new Position(59, 8), new Position(59, 13))), $result);
|
'laboris commodo ad commodo velit mollit qui non officia id. Nulla duis veniam' . "\n" .
|
||||||
|
'veniam officia deserunt et non dolore mollit ea quis eiusmod sit non. Occaecat' . "\n" .
|
||||||
|
'consequat sunt culpa exercitation pariatur id reprehenderit nisi incididunt Lorem' . "\n" .
|
||||||
|
'sint. Officia culpa pariatur laborum nostrud cupidatat consequat mollit.'
|
||||||
|
], new Range(new Position(59, 8), new Position(59, 13))), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use Amp\Deferred;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use Amp\Delayed;
|
||||||
use LanguageServer\{
|
use Amp\Loop;
|
||||||
Server, Client, LanguageClient, ClientHandler, PhpDocumentLoader, DefinitionResolver
|
|
||||||
};
|
|
||||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
|
||||||
use LanguageServerProtocol\{TextDocumentItem, DiagnosticSeverity};
|
|
||||||
use Sabre\Event\Promise;
|
|
||||||
use JsonMapper;
|
use JsonMapper;
|
||||||
|
use LanguageServer\{Client, ClientHandler, DefinitionResolver, LanguageClient, PhpDocumentLoader, Server};
|
||||||
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
|
use LanguageServer\Index\{DependenciesIndex, Index, ProjectIndex};
|
||||||
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
|
use LanguageServerProtocol\{DiagnosticSeverity, TextDocumentItem};
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class ParseErrorsTest extends TestCase
|
class ParseErrorsTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -21,22 +21,31 @@ class ParseErrorsTest extends TestCase
|
||||||
*/
|
*/
|
||||||
private $textDocument;
|
private $textDocument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Deferred
|
||||||
|
*/
|
||||||
private $args;
|
private $args;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$client->textDocument = new class($this->args) extends Client\TextDocument {
|
$this->args = new Deferred();
|
||||||
|
$client->textDocument = new class($this->args) extends Client\TextDocument
|
||||||
|
{
|
||||||
|
/** @var Deferred */
|
||||||
private $args;
|
private $args;
|
||||||
public function __construct(&$args)
|
|
||||||
|
public function __construct($args)
|
||||||
{
|
{
|
||||||
parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream), new JsonMapper);
|
parent::__construct(new ClientHandler(new MockProtocolStream, new MockProtocolStream), new JsonMapper);
|
||||||
$this->args = &$args;
|
$this->args = $args;
|
||||||
}
|
}
|
||||||
public function publishDiagnostics(string $uri, array $diagnostics): Promise
|
|
||||||
|
public function publishDiagnostics(string $uri, array $diagnostics): \Generator
|
||||||
{
|
{
|
||||||
$this->args = func_get_args();
|
$this->args->resolve(func_get_args());
|
||||||
return Promise\resolve(null);
|
yield new Delayed(0);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
|
@ -57,98 +66,102 @@ class ParseErrorsTest extends TestCase
|
||||||
|
|
||||||
public function testParseErrorsArePublishedAsDiagnostics()
|
public function testParseErrorsArePublishedAsDiagnostics()
|
||||||
{
|
{
|
||||||
$this->openFile(__DIR__ . '/../../../fixtures/invalid_file.php');
|
Loop::run(function () {
|
||||||
$this->assertEquals([
|
$this->openFile(__DIR__ . '/../../../fixtures/invalid_file.php');
|
||||||
'whatever',
|
$this->assertEquals([
|
||||||
[[
|
'whatever',
|
||||||
'range' => [
|
[[
|
||||||
'start' => [
|
'range' => [
|
||||||
'line' => 2,
|
'start' => [
|
||||||
'character' => 9
|
'line' => 2,
|
||||||
|
'character' => 9
|
||||||
|
],
|
||||||
|
'end' => [
|
||||||
|
'line' => 2,
|
||||||
|
'character' => 9
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'end' => [
|
'severity' => DiagnosticSeverity::ERROR,
|
||||||
'line' => 2,
|
'code' => null,
|
||||||
'character' => 9
|
'source' => 'php',
|
||||||
]
|
'message' => "'Name' expected."
|
||||||
],
|
],
|
||||||
'severity' => DiagnosticSeverity::ERROR,
|
[
|
||||||
'code' => null,
|
'range' => [
|
||||||
'source' => 'php',
|
'start' => [
|
||||||
'message' => "'Name' expected."
|
'line' => 2,
|
||||||
],
|
'character' => 9
|
||||||
[
|
],
|
||||||
'range' => [
|
'end' => [
|
||||||
'start' => [
|
'line' => 2,
|
||||||
'line' => 2,
|
'character' => 9
|
||||||
'character' => 9
|
]
|
||||||
|
],
|
||||||
|
'severity' => DiagnosticSeverity::ERROR,
|
||||||
|
'code' => null,
|
||||||
|
'source' => 'php',
|
||||||
|
'message' => "'{' expected."
|
||||||
],
|
],
|
||||||
'end' => [
|
[
|
||||||
'line' => 2,
|
'range' => [
|
||||||
'character' => 9
|
'start' => [
|
||||||
]
|
'line' => 2,
|
||||||
],
|
'character' => 9
|
||||||
'severity' => DiagnosticSeverity::ERROR,
|
],
|
||||||
'code' => null,
|
'end' => [
|
||||||
'source' => 'php',
|
'line' => 2,
|
||||||
'message' => "'{' expected."
|
'character' => 9
|
||||||
],
|
]
|
||||||
[
|
],
|
||||||
'range' => [
|
'severity' => DiagnosticSeverity::ERROR,
|
||||||
'start' => [
|
'code' => null,
|
||||||
'line' => 2,
|
'source' => 'php',
|
||||||
'character' => 9
|
'message' => "'}' expected."
|
||||||
],
|
],
|
||||||
'end' => [
|
[
|
||||||
'line' => 2,
|
'range' => [
|
||||||
'character' => 9
|
'start' => [
|
||||||
]
|
'line' => 2,
|
||||||
],
|
'character' => 15
|
||||||
'severity' => DiagnosticSeverity::ERROR,
|
],
|
||||||
'code' => null,
|
'end' => [
|
||||||
'source' => 'php',
|
'line' => 2,
|
||||||
'message' => "'}' expected."
|
'character' => 15
|
||||||
],
|
]
|
||||||
[
|
],
|
||||||
'range' => [
|
'severity' => DiagnosticSeverity::ERROR,
|
||||||
'start' => [
|
'code' => null,
|
||||||
'line' => 2,
|
'source' => 'php',
|
||||||
'character' => 15
|
'message' => "'Name' expected."
|
||||||
],
|
]]
|
||||||
'end' => [
|
], json_decode(json_encode(yield $this->args->promise()), true));
|
||||||
'line' => 2,
|
});
|
||||||
'character' => 15
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'severity' => DiagnosticSeverity::ERROR,
|
|
||||||
'code' => null,
|
|
||||||
'source' => 'php',
|
|
||||||
'message' => "'Name' expected."
|
|
||||||
]]
|
|
||||||
], json_decode(json_encode($this->args), true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testParseErrorsWithOnlyStartLine()
|
public function testParseErrorsWithOnlyStartLine()
|
||||||
{
|
{
|
||||||
$this->markTestIncomplete('This diagnostic not yet implemented in tolerant-php-parser');
|
Loop::run(function () {
|
||||||
$this->openFile(__DIR__ . '/../../../fixtures/namespace_not_first.php');
|
$this->markTestIncomplete('This diagnostic not yet implemented in tolerant-php-parser');
|
||||||
$this->assertEquals([
|
yield $this->openFile(__DIR__ . '/../../../fixtures/namespace_not_first.php');
|
||||||
'whatever',
|
$this->assertEquals([
|
||||||
[[
|
'whatever',
|
||||||
'range' => [
|
[[
|
||||||
'start' => [
|
'range' => [
|
||||||
'line' => 4,
|
'start' => [
|
||||||
'character' => 0
|
'line' => 4,
|
||||||
|
'character' => 0
|
||||||
|
],
|
||||||
|
'end' => [
|
||||||
|
'line' => 4,
|
||||||
|
'character' => 0
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'end' => [
|
'severity' => DiagnosticSeverity::ERROR,
|
||||||
'line' => 4,
|
'code' => null,
|
||||||
'character' => 0
|
'source' => 'php',
|
||||||
]
|
'message' => "Namespace declaration statement has to be the very first statement in the script"
|
||||||
],
|
]]
|
||||||
'severity' => DiagnosticSeverity::ERROR,
|
], json_decode(json_encode(yield $this->args->promise()), true));
|
||||||
'code' => null,
|
});
|
||||||
'source' => 'php',
|
|
||||||
'message' => "Namespace declaration statement has to be the very first statement in the script"
|
|
||||||
]]
|
|
||||||
], json_decode(json_encode($this->args), true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\References;
|
namespace LanguageServer\Tests\Server\TextDocument\References;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
LanguageClient, PhpDocumentLoader, Server, DefinitionResolver
|
LanguageClient, PhpDocumentLoader, Server, DefinitionResolver
|
||||||
};
|
};
|
||||||
|
@ -20,49 +21,55 @@ class GlobalFallbackTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
$projectIndex = new ProjectIndex(new Index, new DependenciesIndex);
|
||||||
$projectIndex->setComplete();
|
$projectIndex->setComplete();
|
||||||
$definitionResolver = new DefinitionResolver($projectIndex);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
$client = new LanguageClient(new MockProtocolStream, new MockProtocolStream);
|
||||||
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
$this->documentLoader = new PhpDocumentLoader(new FileSystemContentRetriever, $projectIndex, $definitionResolver);
|
||||||
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
$this->textDocument = new Server\TextDocument($this->documentLoader, $definitionResolver, $client, $projectIndex);
|
||||||
$this->documentLoader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
$this->documentLoader->open('global_fallback', file_get_contents(__DIR__ . '/../../../../fixtures/global_fallback.php'));
|
||||||
$this->documentLoader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
$this->documentLoader->open('global_symbols', file_get_contents(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testClassDoesNotFallback()
|
public function testClassDoesNotFallback()
|
||||||
{
|
{
|
||||||
// class TestClass implements TestInterface
|
Loop::run(function () {
|
||||||
// Get references for TestClass
|
// class TestClass implements TestInterface
|
||||||
$result = $this->textDocument->references(
|
// Get references for TestClass
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier('global_symbols'),
|
new ReferenceContext,
|
||||||
new Position(6, 9)
|
new TextDocumentIdentifier('global_symbols'),
|
||||||
)->wait();
|
new Position(6, 9)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFallsBackForConstants()
|
public function testFallsBackForConstants()
|
||||||
{
|
{
|
||||||
// const TEST_CONST = 123;
|
Loop::run(function () {
|
||||||
// Get references for TEST_CONST
|
// const TEST_CONST = 123;
|
||||||
$result = $this->textDocument->references(
|
// Get references for TEST_CONST
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier('global_symbols'),
|
new ReferenceContext,
|
||||||
new Position(9, 13)
|
new TextDocumentIdentifier('global_symbols'),
|
||||||
)->wait();
|
new Position(9, 13)
|
||||||
$this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result);
|
);
|
||||||
|
$this->assertEquals([new Location('global_fallback', new Range(new Position(6, 5), new Position(6, 15)))], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFallsBackForFunctions()
|
public function testFallsBackForFunctions()
|
||||||
{
|
{
|
||||||
// function test_function()
|
Loop::run(function () {
|
||||||
// Get references for test_function
|
// function test_function()
|
||||||
$result = $this->textDocument->references(
|
// Get references for test_function
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier('global_symbols'),
|
new ReferenceContext,
|
||||||
new Position(78, 16)
|
new TextDocumentIdentifier('global_symbols'),
|
||||||
)->wait();
|
new Position(78, 16)
|
||||||
$this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result);
|
);
|
||||||
|
$this->assertEquals([new Location('global_fallback', new Range(new Position(5, 0), new Position(5, 13)))], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\References;
|
namespace LanguageServer\Tests\Server\TextDocument\References;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServerProtocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range};
|
use LanguageServerProtocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range};
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
@ -11,191 +12,219 @@ class GlobalTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testReferencesForClassLike()
|
public function testReferencesForClassLike()
|
||||||
{
|
{
|
||||||
// class TestClass implements TestInterface
|
Loop::run(function () {
|
||||||
// Get references for TestClass
|
// class TestClass implements TestInterface
|
||||||
$definition = $this->getDefinitionLocation('TestClass');
|
// Get references for TestClass
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForClassConstants()
|
public function testReferencesForClassConstants()
|
||||||
{
|
{
|
||||||
// const TEST_CLASS_CONST = 123;
|
Loop::run(function () {
|
||||||
// Get references for TEST_CLASS_CONST
|
// const TEST_CLASS_CONST = 123;
|
||||||
$definition = $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST');
|
// Get references for TEST_CLASS_CONST
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass::TEST_CLASS_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass::TEST_CLASS_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForConstants()
|
public function testReferencesForConstants()
|
||||||
{
|
{
|
||||||
// const TEST_CONST = 123;
|
Loop::run(function () {
|
||||||
// Get references for TEST_CONST
|
// const TEST_CONST = 123;
|
||||||
$definition = $this->getDefinitionLocation('TEST_CONST');
|
// Get references for TEST_CONST
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TEST_CONST');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TEST_CONST'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TEST_CONST'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForStaticMethods()
|
public function testReferencesForStaticMethods()
|
||||||
{
|
{
|
||||||
// public static function staticTestMethod()
|
Loop::run(function () {
|
||||||
// Get references for staticTestMethod
|
// public static function staticTestMethod()
|
||||||
$definition = $this->getDefinitionLocation('TestClass::staticTestMethod()');
|
// Get references for staticTestMethod
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass::staticTestMethod()');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForStaticProperties()
|
public function testReferencesForStaticProperties()
|
||||||
{
|
{
|
||||||
// public static $staticTestProperty;
|
Loop::run(function () {
|
||||||
// Get references for $staticTestProperty
|
// public static $staticTestProperty;
|
||||||
$definition = $this->getDefinitionLocation('TestClass::staticTestProperty');
|
// Get references for $staticTestProperty
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass::staticTestProperty');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass::staticTestProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForMethods()
|
public function testReferencesForMethods()
|
||||||
{
|
{
|
||||||
// public function testMethod($testParameter)
|
Loop::run(function () {
|
||||||
// Get references for testMethod
|
// public function testMethod($testParameter)
|
||||||
$definition = $this->getDefinitionLocation('TestClass::testMethod()');
|
// Get references for testMethod
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass::testMethod()');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass::testMethod()'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass::testMethod()'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForProperties()
|
public function testReferencesForProperties()
|
||||||
{
|
{
|
||||||
// public $testProperty;
|
Loop::run(function () {
|
||||||
// Get references for testProperty
|
// public $testProperty;
|
||||||
$definition = $this->getDefinitionLocation('TestClass::testProperty');
|
// Get references for testProperty
|
||||||
$result = $this->textDocument->references(
|
$definition = $this->getDefinitionLocation('TestClass::testProperty');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->start
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass::testProperty'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass::testProperty'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForVariables()
|
public function testReferencesForVariables()
|
||||||
{
|
{
|
||||||
// $var = 123;
|
Loop::run(function () {
|
||||||
// Get definition for $var
|
// $var = 123;
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get definition for $var
|
||||||
$result = $this->textDocument->references(
|
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($uri),
|
new ReferenceContext,
|
||||||
new Position(12, 3)
|
new TextDocumentIdentifier($uri),
|
||||||
)->wait();
|
new Position(12, 3)
|
||||||
$this->assertEquals([
|
);
|
||||||
new Location($uri, new Range(new Position(12, 0), new Position(12, 4))),
|
$this->assertEquals([
|
||||||
new Location($uri, new Range(new Position(13, 5), new Position(13, 9))),
|
new Location($uri, new Range(new Position(12, 0), new Position(12, 4))),
|
||||||
new Location($uri, new Range(new Position(26, 9), new Position(26, 13)))
|
new Location($uri, new Range(new Position(13, 5), new Position(13, 9))),
|
||||||
], $result);
|
new Location($uri, new Range(new Position(26, 9), new Position(26, 13)))
|
||||||
|
], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForFunctionParams()
|
public function testReferencesForFunctionParams()
|
||||||
{
|
{
|
||||||
// function whatever(TestClass $param): TestClass
|
Loop::run(function () {
|
||||||
// Get references for $param
|
// function whatever(TestClass $param): TestClass
|
||||||
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get references for $param
|
||||||
$result = $this->textDocument->references(
|
$uri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($uri),
|
new ReferenceContext,
|
||||||
new Position(21, 32)
|
new TextDocumentIdentifier($uri),
|
||||||
)->wait();
|
new Position(21, 32)
|
||||||
$this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result);
|
);
|
||||||
|
$this->assertEquals([new Location($uri, new Range(new Position(22, 9), new Position(22, 15)))], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForFunctions()
|
public function testReferencesForFunctions()
|
||||||
{
|
{
|
||||||
// function test_function()
|
Loop::run(function () {
|
||||||
// Get references for test_function
|
// function test_function()
|
||||||
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
// Get references for test_function
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php'));
|
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/references.php'));
|
||||||
$result = $this->textDocument->references(
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/symbols.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($symbolsUri),
|
new ReferenceContext,
|
||||||
new Position(78, 16)
|
new TextDocumentIdentifier($symbolsUri),
|
||||||
)->wait();
|
new Position(78, 16)
|
||||||
$this->assertEquals([
|
);
|
||||||
new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
$this->assertEquals([
|
||||||
new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
new Location($referencesUri, new Range(new Position(10, 0), new Position(10, 13))),
|
||||||
], $result);
|
new Location($referencesUri, new Range(new Position(31, 13), new Position(31, 40)))
|
||||||
|
], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForReference()
|
public function testReferencesForReference()
|
||||||
{
|
{
|
||||||
// $obj = new TestClass();
|
Loop::run(function () {
|
||||||
// Get references for TestClass
|
// $obj = new TestClass();
|
||||||
$reference = $this->getReferenceLocations('TestClass')[1];
|
// Get references for TestClass
|
||||||
$result = $this->textDocument->references(
|
$reference = $this->getReferenceLocations('TestClass')[1];
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($reference->uri),
|
new ReferenceContext,
|
||||||
$reference->range->start
|
new TextDocumentIdentifier($reference->uri),
|
||||||
)->wait();
|
$reference->range->start
|
||||||
$this->assertEquals($this->getReferenceLocations('TestClass'), $result);
|
);
|
||||||
|
$this->assertEquals($this->getReferenceLocations('TestClass'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForUnusedClass()
|
public function testReferencesForUnusedClass()
|
||||||
{
|
{
|
||||||
// class UnusedClass
|
Loop::run(function () {
|
||||||
// Get references for UnusedClass
|
// class UnusedClass
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
// Get references for UnusedClass
|
||||||
$result = $this->textDocument->references(
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($symbolsUri),
|
new ReferenceContext,
|
||||||
new Position(111, 10)
|
new TextDocumentIdentifier($symbolsUri),
|
||||||
)->wait();
|
new Position(111, 10)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForUnusedProperty()
|
public function testReferencesForUnusedProperty()
|
||||||
{
|
{
|
||||||
// public $unusedProperty
|
Loop::run(function () {
|
||||||
// Get references for unusedProperty
|
// public $unusedProperty
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
// Get references for unusedProperty
|
||||||
$result = $this->textDocument->references(
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($symbolsUri),
|
new ReferenceContext,
|
||||||
new Position(113, 18)
|
new TextDocumentIdentifier($symbolsUri),
|
||||||
)->wait();
|
new Position(113, 18)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReferencesForUnusedMethod()
|
public function testReferencesForUnusedMethod()
|
||||||
{
|
{
|
||||||
// public function unusedMethod()
|
Loop::run(function () {
|
||||||
// Get references for unusedMethod
|
// public function unusedMethod()
|
||||||
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
// Get references for unusedMethod
|
||||||
$result = $this->textDocument->references(
|
$symbolsUri = pathToUri(realpath(__DIR__ . '/../../../../fixtures/global_symbols.php'));
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($symbolsUri),
|
new ReferenceContext,
|
||||||
new Position(115, 26)
|
new TextDocumentIdentifier($symbolsUri),
|
||||||
)->wait();
|
new Position(115, 26)
|
||||||
$this->assertEquals([], $result);
|
);
|
||||||
|
$this->assertEquals([], $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument\References;
|
namespace LanguageServer\Tests\Server\TextDocument\References;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServerProtocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range};
|
use LanguageServerProtocol\{TextDocumentIdentifier, Position, ReferenceContext, Location, Range};
|
||||||
use function LanguageServer\pathToUri;
|
use function LanguageServer\pathToUri;
|
||||||
|
|
||||||
|
@ -20,14 +21,16 @@ class NamespacedTest extends GlobalTest
|
||||||
|
|
||||||
public function testReferencesForNamespaces()
|
public function testReferencesForNamespaces()
|
||||||
{
|
{
|
||||||
// namespace TestNamespace;
|
Loop::run(function () {
|
||||||
// Get references for TestNamespace
|
// namespace TestNamespace;
|
||||||
$definition = parent::getDefinitionLocation('TestNamespace');
|
// Get references for TestNamespace
|
||||||
$result = $this->textDocument->references(
|
$definition = parent::getDefinitionLocation('TestNamespace');
|
||||||
new ReferenceContext,
|
$result = yield $this->textDocument->references(
|
||||||
new TextDocumentIdentifier($definition->uri),
|
new ReferenceContext,
|
||||||
$definition->range->end
|
new TextDocumentIdentifier($definition->uri),
|
||||||
)->wait();
|
$definition->range->end
|
||||||
$this->assertEquals(parent::getReferenceLocations('TestNamespace'), $result);
|
);
|
||||||
|
$this->assertEquals(parent::getReferenceLocations('TestNamespace'), $result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\TextDocument;
|
namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\{
|
use LanguageServer\{
|
||||||
|
@ -50,13 +51,15 @@ class SignatureHelpTest extends TestCase
|
||||||
*/
|
*/
|
||||||
public function testSignatureHelp(Position $position, SignatureHelp $expectedSignature)
|
public function testSignatureHelp(Position $position, SignatureHelp $expectedSignature)
|
||||||
{
|
{
|
||||||
$callsUri = pathToUri(__DIR__ . '/../../../fixtures/signature_help/calls.php');
|
Loop::run(function () use ($position, $expectedSignature) {
|
||||||
$this->loader->open($callsUri, file_get_contents($callsUri));
|
$callsUri = pathToUri(__DIR__ . '/../../../fixtures/signature_help/calls.php');
|
||||||
$signatureHelp = $this->textDocument->signatureHelp(
|
$this->loader->open($callsUri, file_get_contents($callsUri));
|
||||||
new TextDocumentIdentifier($callsUri),
|
$signatureHelp = yield $this->textDocument->signatureHelp(
|
||||||
$position
|
new TextDocumentIdentifier($callsUri),
|
||||||
)->wait();
|
$position
|
||||||
$this->assertEquals($expectedSignature, $signatureHelp);
|
);
|
||||||
|
$this->assertEquals($expectedSignature, $signatureHelp);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signatureHelpProvider(): array
|
public function signatureHelpProvider(): array
|
||||||
|
|
|
@ -1,42 +1,46 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\Workspace;
|
namespace LanguageServer\Tests\Server\Workspace;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\{DefinitionResolver, LanguageClient, PhpDocumentLoader, Server};
|
use LanguageServer\{DefinitionResolver, Event\MessageEvent, LanguageClient, PhpDocumentLoader, Server};
|
||||||
use LanguageServer\Index\{DependenciesIndex, Index, ProjectIndex};
|
use LanguageServer\Index\{DependenciesIndex, Index, ProjectIndex};
|
||||||
use LanguageServerProtocol\{FileChangeType, FileEvent};
|
use LanguageServerProtocol\{FileChangeType, FileEvent};
|
||||||
use LanguageServer\Message;
|
use LanguageServer\Message;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServer\Server\Workspace;
|
use LanguageServer\Server\Workspace;
|
||||||
use Sabre\Event\Loop;
|
|
||||||
|
|
||||||
class DidChangeWatchedFilesTest extends ServerTestCase
|
class DidChangeWatchedFilesTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testDeletingFileClearsAllDiagnostics()
|
public function testDeletingFileClearsAllDiagnostics()
|
||||||
{
|
{
|
||||||
$client = new LanguageClient(new MockProtocolStream(), $writer = new MockProtocolStream());
|
Loop::run(function () {
|
||||||
$projectIndex = new ProjectIndex($sourceIndex = new Index(), $dependenciesIndex = new DependenciesIndex());
|
$client = new LanguageClient(new MockProtocolStream(), $writer = new MockProtocolStream());
|
||||||
$definitionResolver = new DefinitionResolver($projectIndex);
|
$projectIndex = new ProjectIndex($sourceIndex = new Index(), $dependenciesIndex = new DependenciesIndex());
|
||||||
$loader = new PhpDocumentLoader(new FileSystemContentRetriever(), $projectIndex, $definitionResolver);
|
$definitionResolver = new DefinitionResolver($projectIndex);
|
||||||
$workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $loader, null);
|
$loader = new PhpDocumentLoader(new FileSystemContentRetriever(), $projectIndex, $definitionResolver);
|
||||||
|
$workspace = new Server\Workspace($client, $projectIndex, $dependenciesIndex, $sourceIndex, null, $loader, null);
|
||||||
|
|
||||||
$fileEvent = new FileEvent('my uri', FileChangeType::DELETED);
|
$fileEvent = new FileEvent('my uri', FileChangeType::DELETED);
|
||||||
|
|
||||||
$isDiagnosticsCleared = false;
|
$isDiagnosticsCleared = false;
|
||||||
$writer->on('message', function (Message $message) use ($fileEvent, &$isDiagnosticsCleared) {
|
$deferred = new Deferred();
|
||||||
if ($message->body->method === "textDocument/publishDiagnostics") {
|
$writer->addListener('message', function (MessageEvent $messageEvent) use ($deferred, $fileEvent, &$isDiagnosticsCleared) {
|
||||||
$this->assertEquals($message->body->params->uri, $fileEvent->uri);
|
$message = $messageEvent->getMessage();
|
||||||
$this->assertEquals($message->body->params->diagnostics, []);
|
if ($message->body->method === "textDocument/publishDiagnostics") {
|
||||||
$isDiagnosticsCleared = true;
|
$this->assertEquals($message->body->params->uri, $fileEvent->uri);
|
||||||
}
|
$this->assertEquals($message->body->params->diagnostics, []);
|
||||||
|
$deferred->resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$workspace->didChangeWatchedFiles([$fileEvent]);
|
||||||
|
|
||||||
|
$this->assertTrue(yield $deferred->promise(), "Deleting file should clear all diagnostics.");
|
||||||
});
|
});
|
||||||
|
|
||||||
$workspace->didChangeWatchedFiles([$fileEvent]);
|
|
||||||
Loop\tick(true);
|
|
||||||
|
|
||||||
$this->assertTrue($isDiagnosticsCleared, "Deleting file should clear all diagnostics.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace LanguageServer\Tests\Server\Workspace;
|
namespace LanguageServer\Tests\Server\Workspace;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\Tests\Server\ServerTestCase;
|
use LanguageServer\Tests\Server\ServerTestCase;
|
||||||
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
|
use LanguageServer\{Server, Client, LanguageClient, Project, PhpDocument};
|
||||||
|
@ -24,65 +25,69 @@ class SymbolTest extends ServerTestCase
|
||||||
{
|
{
|
||||||
public function testEmptyQueryReturnsAllSymbols()
|
public function testEmptyQueryReturnsAllSymbols()
|
||||||
{
|
{
|
||||||
// Request symbols
|
Loop::run(function () {
|
||||||
$result = $this->workspace->symbol('')->wait();
|
// Request symbols
|
||||||
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
$result = yield $this->workspace->symbol('');
|
||||||
|
$referencesUri = pathToUri(realpath(__DIR__ . '/../../../fixtures/references.php'));
|
||||||
|
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''),
|
new SymbolInformation('TestNamespace', SymbolKind::NAMESPACE, new Location($referencesUri, new Range(new Position(2, 0), new Position(2, 24))), ''),
|
||||||
// Namespaced
|
// Namespaced
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TEST_CONST'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestNamespace\\TestClass::TEST_CLASS_CONST'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestNamespace\\TestClass::testProperty'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\TestTrait'), 'TestNamespace'),
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestNamespace\\TestInterface'), 'TestNamespace'),
|
||||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\test_function()'), 'TestNamespace'),
|
||||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\ChildClass'), 'TestNamespace'),
|
||||||
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'),
|
new SymbolInformation('Example', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\Example'), 'TestNamespace'),
|
||||||
new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
new SymbolInformation('__construct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__construct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
new SymbolInformation('__destruct', SymbolKind::CONSTRUCTOR, $this->getDefinitionLocation('TestNamespace\\Example::__destruct'), 'TestNamespace\\Example'),
|
||||||
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
new SymbolInformation('TestNamespace\\InnerNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('TestNamespace\\InnerNamespace'), 'TestNamespace'),
|
||||||
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
new SymbolInformation('InnerClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestNamespace\\InnerNamespace\\InnerClass'), 'TestNamespace\\InnerNamespace'),
|
||||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('TestNamespace\\whatever()'), 'TestNamespace'),
|
||||||
// Global
|
// Global
|
||||||
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
new SymbolInformation('TEST_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_CONST'), ''),
|
||||||
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''),
|
new SymbolInformation('TestClass', SymbolKind::CLASS_, $this->getDefinitionLocation('TestClass'), ''),
|
||||||
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'),
|
new SymbolInformation('TEST_CLASS_CONST', SymbolKind::CONSTANT, $this->getDefinitionLocation('TestClass::TEST_CLASS_CONST'), 'TestClass'),
|
||||||
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'),
|
new SymbolInformation('staticTestProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::staticTestProperty'), 'TestClass'),
|
||||||
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'),
|
new SymbolInformation('testProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('TestClass::testProperty'), 'TestClass'),
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass'),
|
||||||
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''),
|
new SymbolInformation('TestTrait', SymbolKind::CLASS_, $this->getDefinitionLocation('TestTrait'), ''),
|
||||||
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''),
|
new SymbolInformation('TestInterface', SymbolKind::INTERFACE, $this->getDefinitionLocation('TestInterface'), ''),
|
||||||
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''),
|
new SymbolInformation('test_function', SymbolKind::FUNCTION, $this->getDefinitionLocation('test_function()'), ''),
|
||||||
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''),
|
new SymbolInformation('ChildClass', SymbolKind::CLASS_, $this->getDefinitionLocation('ChildClass'), ''),
|
||||||
new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''),
|
new SymbolInformation('TEST_DEFINE_CONSTANT', SymbolKind::CONSTANT, $this->getDefinitionLocation('TEST_DEFINE_CONSTANT'), ''),
|
||||||
new SymbolInformation('UnusedClass', SymbolKind::CLASS_, $this->getDefinitionLocation('UnusedClass'), ''),
|
new SymbolInformation('UnusedClass', SymbolKind::CLASS_, $this->getDefinitionLocation('UnusedClass'), ''),
|
||||||
new SymbolInformation('unusedProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('UnusedClass::unusedProperty'), 'UnusedClass'),
|
new SymbolInformation('unusedProperty', SymbolKind::PROPERTY, $this->getDefinitionLocation('UnusedClass::unusedProperty'), 'UnusedClass'),
|
||||||
new SymbolInformation('unusedMethod', SymbolKind::METHOD, $this->getDefinitionLocation('UnusedClass::unusedMethod'), 'UnusedClass'),
|
new SymbolInformation('unusedMethod', SymbolKind::METHOD, $this->getDefinitionLocation('UnusedClass::unusedMethod'), 'UnusedClass'),
|
||||||
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''),
|
new SymbolInformation('whatever', SymbolKind::FUNCTION, $this->getDefinitionLocation('whatever()'), ''),
|
||||||
|
|
||||||
new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), ''),
|
new SymbolInformation('SecondTestNamespace', SymbolKind::NAMESPACE, $this->getDefinitionLocation('SecondTestNamespace'), ''),
|
||||||
], $result);
|
], $result);
|
||||||
// @codingStandardsIgnoreEnd
|
// @codingStandardsIgnoreEnd
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testQueryFiltersResults()
|
public function testQueryFiltersResults()
|
||||||
{
|
{
|
||||||
// Request symbols
|
Loop::run(function () {
|
||||||
$result = $this->workspace->symbol('testmethod')->wait();
|
// Request symbols
|
||||||
// @codingStandardsIgnoreStart
|
$result = yield $this->workspace->symbol('testmethod');
|
||||||
$this->assertEquals([
|
// @codingStandardsIgnoreStart
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
$this->assertEquals([
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::staticTestMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestNamespace\\TestClass::testMethod()'), 'TestNamespace\\TestClass'),
|
||||||
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass')
|
new SymbolInformation('staticTestMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::staticTestMethod()'), 'TestClass'),
|
||||||
], $result);
|
new SymbolInformation('testMethod', SymbolKind::METHOD, $this->getDefinitionLocation('TestClass::testMethod()'), 'TestClass')
|
||||||
// @codingStandardsIgnoreEnd
|
], $result);
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
use function LanguageServer\{pathToUri, timeout};
|
use function LanguageServer\{pathToUri};
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
],
|
],
|
||||||
"LanguageServer\\pathToUri()": [
|
"LanguageServer\\pathToUri()": [
|
||||||
"./functionUse2.php"
|
"./functionUse2.php"
|
||||||
],
|
|
||||||
"LanguageServer\\timeout()": [
|
|
||||||
"./functionUse2.php"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"definitions": []
|
"definitions": []
|
||||||
|
|
Loading…
Reference in New Issue