mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Update Amp usage
Fixed a few errors and used byte-stream for reading and writing.
This commit is contained in:
parent
263a4c8cf1
commit
b0d97843ce
@ -22,7 +22,8 @@
|
|||||||
"webmozart/glob": "^4.1",
|
"webmozart/glob": "^4.1",
|
||||||
"webmozart/path-util": "^2.3",
|
"webmozart/path-util": "^2.3",
|
||||||
"symfony/console": "^3.0||^4.0",
|
"symfony/console": "^3.0||^4.0",
|
||||||
"amphp/amp": "^2.1"
|
"amphp/amp": "^2.1",
|
||||||
|
"amphp/byte-stream": "^1.5"
|
||||||
},
|
},
|
||||||
"bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"],
|
"bin": ["psalm", "psalter", "psalm-language-server", "psalm-plugin"],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
</ignoreExceptions>
|
</ignoreExceptions>
|
||||||
|
|
||||||
<stubs>
|
<stubs>
|
||||||
<file name="src/Psalm/Internal/Stubs/SabreEvent.php"/>
|
<file name="src/Psalm/Internal/Stubs/Amp.php"/>
|
||||||
</stubs>
|
</stubs>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
@ -7,6 +7,7 @@ use Psalm\Internal\LanguageServer\ClientHandler;
|
|||||||
use LanguageServerProtocol\{Diagnostic, TextDocumentItem, TextDocumentIdentifier};
|
use LanguageServerProtocol\{Diagnostic, TextDocumentItem, TextDocumentIdentifier};
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
use JsonMapper;
|
use JsonMapper;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all textDocument/* methods
|
* Provides method handlers for all textDocument/* methods
|
||||||
@ -53,21 +54,15 @@ class TextDocument
|
|||||||
*/
|
*/
|
||||||
public function xcontent(TextDocumentIdentifier $textDocument): Promise
|
public function xcontent(TextDocumentIdentifier $textDocument): Promise
|
||||||
{
|
{
|
||||||
$promise = $this->handler->request(
|
return call(
|
||||||
'textDocument/xcontent',
|
function () use ($textDocument) {
|
||||||
['textDocument' => $textDocument]
|
$result = yield $this->handler->request(
|
||||||
);
|
'textDocument/xcontent',
|
||||||
|
['textDocument' => $textDocument]
|
||||||
|
);
|
||||||
|
|
||||||
$promise->onResolve(
|
|
||||||
/**
|
|
||||||
* @param object $result
|
|
||||||
* @return object
|
|
||||||
*/
|
|
||||||
function ($result) {
|
|
||||||
return $this->mapper->map($result, new TextDocumentItem);
|
return $this->mapper->map($result, new TextDocumentItem);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return $promise;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ declare(strict_types = 1);
|
|||||||
namespace Psalm\Internal\LanguageServer;
|
namespace Psalm\Internal\LanguageServer;
|
||||||
|
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
|
use Amp\Deferred;
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -38,54 +40,48 @@ 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 Resolved with the result of the request or rejected with an error
|
* @return Promise <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): Promise
|
||||||
{
|
{
|
||||||
$id = $this->idGenerator->generate();
|
$id = $this->idGenerator->generate();
|
||||||
|
|
||||||
$promise = $this->protocolWriter->write(
|
return call(function () use ($id, $method, $params) {
|
||||||
new Message(
|
yield $this->protocolWriter->write(
|
||||||
new AdvancedJsonRpc\Request($id, $method, (object)$params)
|
new Message(
|
||||||
)
|
new AdvancedJsonRpc\Request($id, $method, (object) $params)
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$promise->onResolve(
|
$deferred = new Deferred();
|
||||||
/**
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
function () use ($id) {
|
|
||||||
$deferred = new \Amp\Deferred();
|
|
||||||
|
|
||||||
$listener =
|
$listener =
|
||||||
|
/**
|
||||||
|
* @param callable $listener
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function (Message $msg) use ($id, $deferred, &$listener) {
|
||||||
|
error_log('request handler');
|
||||||
/**
|
/**
|
||||||
* @param callable $listener
|
* @psalm-suppress UndefinedPropertyFetch
|
||||||
* @return void
|
* @psalm-suppress MixedArgument
|
||||||
*/
|
*/
|
||||||
function (Message $msg) use ($id, $deferred, &$listener) {
|
if ($msg->body
|
||||||
/**
|
&& AdvancedJsonRpc\Response::isResponse($msg->body)
|
||||||
* @psalm-suppress UndefinedPropertyFetch
|
&& $msg->body->id === $id
|
||||||
* @psalm-suppress MixedArgument
|
) {
|
||||||
*/
|
// Received a response
|
||||||
if ($msg->body
|
$this->protocolReader->removeListener('message', $listener);
|
||||||
&& AdvancedJsonRpc\Response::isResponse($msg->body)
|
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
||||||
&& $msg->body->id === $id
|
$deferred->resolve($msg->body->result);
|
||||||
) {
|
} else {
|
||||||
// Received a response
|
$deferred->fail($msg->body->error);
|
||||||
$this->protocolReader->removeListener('message', $listener);
|
|
||||||
if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
|
|
||||||
$deferred->resolve($msg->body->result);
|
|
||||||
} else {
|
|
||||||
$deferred->fail($msg->body->error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
$this->protocolReader->on('message', $listener);
|
};
|
||||||
return $deferred->promise();
|
$this->protocolReader->on('message', $listener);
|
||||||
}
|
return $deferred->promise();
|
||||||
);
|
});
|
||||||
|
|
||||||
return $promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,9 +26,10 @@ use Psalm\Internal\LanguageServer\Server\TextDocument;
|
|||||||
use LanguageServerProtocol\{Range, Position, Diagnostic, DiagnosticSeverity};
|
use LanguageServerProtocol\{Range, Position, Diagnostic, DiagnosticSeverity};
|
||||||
use AdvancedJsonRpc;
|
use AdvancedJsonRpc;
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
use function Amp\coroutine;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Webmozart\PathUtil\Path;
|
use Webmozart\PathUtil\Path;
|
||||||
|
use function Amp\call;
|
||||||
|
use function Amp\asyncCoroutine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -100,70 +101,60 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||||||
$this->protocolReader->on(
|
$this->protocolReader->on(
|
||||||
'message',
|
'message',
|
||||||
/** @return void */
|
/** @return void */
|
||||||
function (Message $msg) {
|
asyncCoroutine(function (Message $msg) {
|
||||||
\Amp\call(
|
if (!$msg->body) {
|
||||||
/** @return \Generator<int, Promise, mixed, void> */
|
return;
|
||||||
function () use ($msg) {
|
}
|
||||||
if (!$msg->body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = null;
|
$result = null;
|
||||||
$error = null;
|
$error = null;
|
||||||
try {
|
try {
|
||||||
// Invoke the method handler to get a result
|
// Invoke the method handler to get a result
|
||||||
/**
|
/**
|
||||||
* @var Promise
|
* @var Promise
|
||||||
* @psalm-suppress UndefinedClass
|
* @psalm-suppress UndefinedClass
|
||||||
*/
|
*/
|
||||||
$dispatched = $this->dispatch($msg->body);
|
$dispatched = $this->dispatch($msg->body);
|
||||||
$result = yield $dispatched;
|
$result = yield $dispatched;
|
||||||
} catch (AdvancedJsonRpc\Error $e) {
|
} catch (AdvancedJsonRpc\Error $e) {
|
||||||
// If a ResponseError is thrown, send it back in the Response
|
// If a ResponseError is thrown, send it back in the Response
|
||||||
$error = $e;
|
$error = $e;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
|
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
|
||||||
$error = new AdvancedJsonRpc\Error(
|
$error = new AdvancedJsonRpc\Error(
|
||||||
(string)$e,
|
(string) $e,
|
||||||
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
|
AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
|
||||||
null,
|
null,
|
||||||
$e
|
$e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Only send a Response for a Request
|
// Only send a Response for a Request
|
||||||
// Notifications do not send Responses
|
// Notifications do not send Responses
|
||||||
/**
|
/**
|
||||||
* @psalm-suppress UndefinedPropertyFetch
|
* @psalm-suppress UndefinedPropertyFetch
|
||||||
* @psalm-suppress MixedArgument
|
* @psalm-suppress MixedArgument
|
||||||
*/
|
*/
|
||||||
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
|
if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
|
||||||
if ($error !== null) {
|
if ($error !== null) {
|
||||||
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
|
$responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
|
||||||
} 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 $this->protocolWriter->write(new Message($responseBody));
|
||||||
}
|
}
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->protocolReader->on(
|
$this->protocolReader->on(
|
||||||
'readMessageGroup',
|
'readMessageGroup',
|
||||||
/** @return void */
|
/** @return void */
|
||||||
function () {
|
function () {
|
||||||
\Amp\call(
|
$this->doAnalysis();
|
||||||
/** @return null */
|
|
||||||
function () {
|
|
||||||
$this->doAnalysis();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -186,7 +177,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||||||
string $rootPath = null,
|
string $rootPath = null,
|
||||||
int $processId = null
|
int $processId = null
|
||||||
): Promise {
|
): Promise {
|
||||||
return \Amp\call(
|
return call(
|
||||||
/** @return \Generator<int, true, mixed, InitializeResult> */
|
/** @return \Generator<int, true, mixed, InitializeResult> */
|
||||||
function () use ($capabilities, $rootPath, $processId) {
|
function () use ($capabilities, $rootPath, $processId) {
|
||||||
// Eventually, this might block on something. Leave it as a generator.
|
// Eventually, this might block on something. Leave it as a generator.
|
||||||
@ -442,22 +433,4 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
|||||||
}
|
}
|
||||||
return $filepath;
|
return $filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws an exception on the next tick.
|
|
||||||
* Useful for letting a promise crash the process on rejection.
|
|
||||||
*
|
|
||||||
* @param Throwable $err
|
|
||||||
* @return void
|
|
||||||
* @psalm-suppress PossiblyUnusedMethod
|
|
||||||
*/
|
|
||||||
public static function crash(Throwable $err)
|
|
||||||
{
|
|
||||||
\Amp\Loop::defer(
|
|
||||||
/** @return void */
|
|
||||||
function () use ($err) {
|
|
||||||
throw $err;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ declare(strict_types = 1);
|
|||||||
namespace Psalm\Internal\LanguageServer;
|
namespace Psalm\Internal\LanguageServer;
|
||||||
|
|
||||||
use AdvancedJsonRpc\Message as MessageBody;
|
use AdvancedJsonRpc\Message as MessageBody;
|
||||||
|
use Amp\ByteStream\ResourceInputStream;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psalm\Internal\LanguageServer\Message;
|
use function Amp\asyncCall;
|
||||||
use Amp\Loop;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamReader.php
|
* Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamReader.php
|
||||||
@ -18,11 +18,6 @@ class ProtocolStreamReader implements ProtocolReader
|
|||||||
const PARSE_HEADERS = 1;
|
const PARSE_HEADERS = 1;
|
||||||
const PARSE_BODY = 2;
|
const PARSE_BODY = 2;
|
||||||
|
|
||||||
/** @var resource */
|
|
||||||
private $input;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $read_watcher;
|
|
||||||
/**
|
/**
|
||||||
* This is checked by ProtocolStreamReader so that it will stop reading from streams in the forked process.
|
* This is checked by ProtocolStreamReader so that it will stop reading from streams in the forked process.
|
||||||
* There could be buffered bytes in stdin/over TCP, those would be processed by TCP if it were not for this check.
|
* There could be buffered bytes in stdin/over TCP, those would be processed by TCP if it were not for this check.
|
||||||
@ -45,49 +40,36 @@ class ProtocolStreamReader implements ProtocolReader
|
|||||||
*/
|
*/
|
||||||
public function __construct($input)
|
public function __construct($input)
|
||||||
{
|
{
|
||||||
$this->input = $input;
|
$input = new ResourceInputStream($input);
|
||||||
|
asyncCall(function () use ($input): \Generator {
|
||||||
$read_watcher = Loop::onReadable(
|
while (($chunk = yield $input->read()) !== null) {
|
||||||
$this->input,
|
/** @var string $chunk */
|
||||||
/** @return void */
|
$this->readMessages($chunk);
|
||||||
function () {
|
|
||||||
if (feof($this->input)) {
|
|
||||||
// If stream_select reported a status change for this stream,
|
|
||||||
// but the stream is EOF, it means it was closed.
|
|
||||||
$this->emitClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!$this->is_accepting_new_requests) {
|
|
||||||
// If we fork, don't read any bytes in the input buffer from the worker process.
|
|
||||||
$this->emitClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$emitted_messages = $this->readMessages();
|
|
||||||
if ($emitted_messages > 0) {
|
|
||||||
$this->emit('readMessageGroup');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$this->read_watcher = $read_watcher;
|
$this->emitClose();
|
||||||
|
});
|
||||||
|
|
||||||
$this->on(
|
$this->on(
|
||||||
'close',
|
'close',
|
||||||
/** @return void */
|
/** @return void */
|
||||||
function () {
|
static function () use ($input) {
|
||||||
Loop::cancel($this->read_watcher);
|
$input->close();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param string $buffer
|
||||||
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function readMessages() : int
|
private function readMessages(string $buffer) : int
|
||||||
{
|
{
|
||||||
$emitted_messages = 0;
|
$emitted_messages = 0;
|
||||||
while (($c = fgetc($this->input)) !== false && $c !== '') {
|
$i = 0;
|
||||||
$this->buffer .= $c;
|
while (($buffer[$i] ?? '') !== '') {
|
||||||
|
$this->buffer .= $buffer[$i++];
|
||||||
switch ($this->parsing_mode) {
|
switch ($this->parsing_mode) {
|
||||||
case self::PARSE_HEADERS:
|
case self::PARSE_HEADERS:
|
||||||
if ($this->buffer === "\r\n") {
|
if ($this->buffer === "\r\n") {
|
||||||
|
@ -3,13 +3,8 @@ declare(strict_types = 1);
|
|||||||
|
|
||||||
namespace Psalm\Internal\LanguageServer;
|
namespace Psalm\Internal\LanguageServer;
|
||||||
|
|
||||||
use Psalm\Internal\LanguageServer\Message;
|
use Amp\ByteStream\ResourceOutputStream;
|
||||||
use Amp\{
|
use Amp\Promise;
|
||||||
Deferred,
|
|
||||||
Loop,
|
|
||||||
Promise
|
|
||||||
};
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -17,26 +12,16 @@ use RuntimeException;
|
|||||||
class ProtocolStreamWriter implements ProtocolWriter
|
class ProtocolStreamWriter implements ProtocolWriter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var resource $output
|
* @var \Amp\ByteStream\ResourceOutputStream
|
||||||
*/
|
*/
|
||||||
private $output;
|
private $output;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ?string
|
|
||||||
*/
|
|
||||||
private $output_watcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, array{message: string, deferred: Deferred}> $messages
|
|
||||||
*/
|
|
||||||
private $messages = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $output
|
* @param resource $output
|
||||||
*/
|
*/
|
||||||
public function __construct($output)
|
public function __construct($output)
|
||||||
{
|
{
|
||||||
$this->output = $output;
|
$this->output = new ResourceOutputStream($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,58 +29,6 @@ class ProtocolStreamWriter implements ProtocolWriter
|
|||||||
*/
|
*/
|
||||||
public function write(Message $msg): Promise
|
public function write(Message $msg): Promise
|
||||||
{
|
{
|
||||||
// if the message queue is currently empty, register a write handler.
|
return $this->output->write((string)$msg);
|
||||||
if (empty($this->messages)) {
|
|
||||||
$this->output_watcher = Loop::onWritable(
|
|
||||||
$this->output,
|
|
||||||
/** @return void */
|
|
||||||
function () {
|
|
||||||
$this->flush();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deferred = new \Amp\Deferred();
|
|
||||||
$this->messages[] = [
|
|
||||||
'message' => (string)$msg,
|
|
||||||
'deferred' => $deferred
|
|
||||||
];
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes pending messages to the output stream.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function flush()
|
|
||||||
{
|
|
||||||
$keepWriting = true;
|
|
||||||
while ($keepWriting) {
|
|
||||||
$message = $this->messages[0]['message'];
|
|
||||||
$deferred = $this->messages[0]['deferred'];
|
|
||||||
|
|
||||||
$bytesWritten = @fwrite($this->output, $message);
|
|
||||||
|
|
||||||
if ($bytesWritten > 0) {
|
|
||||||
$message = substr($message, $bytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if this message was completely sent
|
|
||||||
if (strlen($message) === 0) {
|
|
||||||
array_shift($this->messages);
|
|
||||||
|
|
||||||
// This was the last message in the queue, remove the write handler.
|
|
||||||
if (count($this->messages) === 0 && $this->output_watcher) {
|
|
||||||
Loop::cancel($this->output_watcher);
|
|
||||||
$keepWriting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$deferred->resolve();
|
|
||||||
} else {
|
|
||||||
$this->messages[0]['message'] = $message;
|
|
||||||
$keepWriting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ use Psalm\Internal\LanguageServer\Index\ReadableIndex;
|
|||||||
use Psalm\Internal\Analyzer\FileAnalyzer;
|
use Psalm\Internal\Analyzer\FileAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||||
use Amp\Promise;
|
use Amp\Promise;
|
||||||
use function Amp\coroutine;
|
use Amp\Success;
|
||||||
use function Psalm\Internal\LanguageServer\{waitForEvent, isVendored};
|
use function Psalm\Internal\LanguageServer\{waitForEvent, isVendored};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -174,45 +174,36 @@ class TextDocument
|
|||||||
*/
|
*/
|
||||||
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return \Amp\call(
|
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||||
/**
|
|
||||||
* @return \Generator<int, true, mixed, Hover|Location>
|
|
||||||
*/
|
|
||||||
function () use ($textDocument, $position) {
|
|
||||||
if (false) {
|
|
||||||
yield true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
try {
|
||||||
|
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||||
|
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||||
|
$this->codebase->file_provider->openFile($file_path);
|
||||||
|
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||||
|
return new Success(new Hover([]));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
if ($reference_location === null) {
|
||||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
return new Success(new Hover([]));
|
||||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
}
|
||||||
$this->codebase->file_provider->openFile($file_path);
|
|
||||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
|
||||||
return new Hover([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($reference_location === null) {
|
list($reference) = $reference_location;
|
||||||
return new Hover([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
list($reference) = $reference_location;
|
$code_location = $this->codebase->getSymbolLocation($file_path, $reference);
|
||||||
|
|
||||||
$code_location = $this->codebase->getSymbolLocation($file_path, $reference);
|
if (!$code_location) {
|
||||||
|
return new Success(new Hover([]));
|
||||||
|
}
|
||||||
|
|
||||||
if (!$code_location) {
|
return new Success(
|
||||||
return new Hover([]);
|
new Location(
|
||||||
}
|
LanguageServer::pathToUri($code_location->file_path),
|
||||||
|
new Range(
|
||||||
return new Location(
|
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
|
||||||
LanguageServer::pathToUri($code_location->file_path),
|
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1)
|
||||||
new Range(
|
)
|
||||||
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
|
)
|
||||||
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,40 +217,29 @@ class TextDocument
|
|||||||
*/
|
*/
|
||||||
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return \Amp\call(
|
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||||
/**
|
|
||||||
* @return \Generator<int, true, mixed, Hover>
|
|
||||||
*/
|
|
||||||
function () use ($textDocument, $position) {
|
|
||||||
if (false) {
|
|
||||||
yield true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
try {
|
||||||
|
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
||||||
|
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
||||||
|
$this->codebase->file_provider->openFile($file_path);
|
||||||
|
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
||||||
|
return new Success(new Hover([]));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
if ($reference_location === null) {
|
||||||
$reference_location = $this->codebase->getReferenceAtPosition($file_path, $position);
|
return new Success(new Hover([]));
|
||||||
} catch (\Psalm\Exception\UnanalyzedFileException $e) {
|
}
|
||||||
$this->codebase->file_provider->openFile($file_path);
|
|
||||||
$this->server->queueFileAnalysis($file_path, $textDocument->uri);
|
|
||||||
return new Hover([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($reference_location === null) {
|
list($reference, $range) = $reference_location;
|
||||||
return new Hover([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
list($reference, $range) = $reference_location;
|
$contents = [];
|
||||||
|
$contents[] = new MarkedString(
|
||||||
$contents = [];
|
'php',
|
||||||
$contents[] = new MarkedString(
|
$this->codebase->getSymbolInformation($file_path, $reference)
|
||||||
'php',
|
|
||||||
$this->codebase->getSymbolInformation($file_path, $reference)
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Hover($contents, $range);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return new Success(new Hover($contents, $range));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -278,92 +258,81 @@ class TextDocument
|
|||||||
*/
|
*/
|
||||||
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
|
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
|
||||||
{
|
{
|
||||||
return \Amp\call(
|
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||||
/**
|
|
||||||
* @return \Generator<int, true, mixed, array<empty, empty>|CompletionList>
|
$completion_data = $this->codebase->getCompletionDataAtPosition($file_path, $position);
|
||||||
*/
|
|
||||||
function () use ($textDocument, $position) {
|
if (!$completion_data) {
|
||||||
if (false) {
|
error_log('completion not found at ' . $position->line . ':' . $position->character);
|
||||||
yield true;
|
return new Success([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
list($recent_type, $gap) = $completion_data;
|
||||||
|
|
||||||
|
error_log('gap: "' . $gap . '" and type: "' . $recent_type . '"');
|
||||||
|
|
||||||
|
$completion_items = [];
|
||||||
|
|
||||||
|
if ($gap === '->' || $gap === '::') {
|
||||||
|
$instance_completion_items = [];
|
||||||
|
$static_completion_items = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$class_storage = $this->codebase->classlike_storage_provider->get($recent_type);
|
||||||
|
|
||||||
|
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
|
||||||
|
$method_storage = $this->codebase->methods->getStorage($declaring_method_id);
|
||||||
|
|
||||||
|
$instance_completion_items[] = new CompletionItem(
|
||||||
|
(string)$method_storage,
|
||||||
|
CompletionItemKind::METHOD,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$method_storage->cased_name . '()'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
|
||||||
|
$property_storage = $this->codebase->properties->getStorage(
|
||||||
|
$declaring_class . '::$' . $property_name
|
||||||
|
);
|
||||||
|
|
||||||
$completion_data = $this->codebase->getCompletionDataAtPosition($file_path, $position);
|
$instance_completion_items[] = new CompletionItem(
|
||||||
|
$property_storage->getInfo() . ' $' . $property_name,
|
||||||
if (!$completion_data) {
|
CompletionItemKind::PROPERTY,
|
||||||
error_log('completion not found at ' . $position->line . ':' . $position->character);
|
null,
|
||||||
return [];
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
($gap === '::' ? '$' : '') . $property_name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
list($recent_type, $gap) = $completion_data;
|
foreach ($class_storage->class_constant_locations as $const_name => $_) {
|
||||||
|
$static_completion_items[] = new CompletionItem(
|
||||||
error_log('gap: "' . $gap . '" and type: "' . $recent_type . '"');
|
'const ' . $const_name,
|
||||||
|
CompletionItemKind::VARIABLE,
|
||||||
$completion_items = [];
|
null,
|
||||||
|
null,
|
||||||
if ($gap === '->' || $gap === '::') {
|
null,
|
||||||
$instance_completion_items = [];
|
null,
|
||||||
$static_completion_items = [];
|
$const_name
|
||||||
|
);
|
||||||
try {
|
|
||||||
$class_storage = $this->codebase->classlike_storage_provider->get($recent_type);
|
|
||||||
|
|
||||||
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
|
|
||||||
$method_storage = $this->codebase->methods->getStorage($declaring_method_id);
|
|
||||||
|
|
||||||
$instance_completion_items[] = new CompletionItem(
|
|
||||||
(string)$method_storage,
|
|
||||||
CompletionItemKind::METHOD,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$method_storage->cased_name . '()'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
|
|
||||||
$property_storage = $this->codebase->properties->getStorage(
|
|
||||||
$declaring_class . '::$' . $property_name
|
|
||||||
);
|
|
||||||
|
|
||||||
$instance_completion_items[] = new CompletionItem(
|
|
||||||
$property_storage->getInfo() . ' $' . $property_name,
|
|
||||||
CompletionItemKind::PROPERTY,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
($gap === '::' ? '$' : '') . $property_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($class_storage->class_constant_locations as $const_name => $_) {
|
|
||||||
$static_completion_items[] = new CompletionItem(
|
|
||||||
'const ' . $const_name,
|
|
||||||
CompletionItemKind::VARIABLE,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$const_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
error_log($e->getMessage());
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$completion_items = $gap === '->'
|
|
||||||
? $instance_completion_items
|
|
||||||
: array_merge($instance_completion_items, $static_completion_items);
|
|
||||||
|
|
||||||
error_log('Found ' . count($completion_items) . ' items');
|
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
return new CompletionList($completion_items, false);
|
error_log($e->getMessage());
|
||||||
|
return new Success([]);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
$completion_items = $gap === '->'
|
||||||
|
? $instance_completion_items
|
||||||
|
: array_merge($instance_completion_items, $static_completion_items);
|
||||||
|
|
||||||
|
error_log('Found ' . count($completion_items) . ' items');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Success(new CompletionList($completion_items, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
src/Psalm/Internal/Stubs/Amp.php
Normal file
41
src/Psalm/Internal/Stubs/Amp.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TReturn
|
||||||
|
* @param callable():\Generator<mixed, mixed, mixed, TReturn> $gen
|
||||||
|
* @return Promise<TReturn>
|
||||||
|
*/
|
||||||
|
function coroutine(callable $gen) : Promise {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TReturn
|
||||||
|
* @param callable():(\Generator<mixed, mixed, mixed, TReturn>|null) $gen
|
||||||
|
* @return Promise<TReturn>
|
||||||
|
*/
|
||||||
|
function call(callable $gen) : Promise {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TReturn
|
||||||
|
*/
|
||||||
|
interface Promise {
|
||||||
|
/**
|
||||||
|
* @param callable(\Throwable|null $exception, TReturn|null $result):void
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onResolve(callable $onResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TReturn
|
||||||
|
*
|
||||||
|
* @template-implements Promise<TReturn>
|
||||||
|
*/
|
||||||
|
class Success implements Promise {
|
||||||
|
/**
|
||||||
|
* @param callable(\Throwable|null $exception, TReturn|null $result):void
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onResolve(callable $onResolved) {}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Amp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TReturn
|
|
||||||
* @param callable():\Generator<mixed, mixed, mixed, TReturn> $gen
|
|
||||||
* @return Promise<TReturn>
|
|
||||||
*/
|
|
||||||
function coroutine(callable $gen) : Promise {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TReturn
|
|
||||||
* @param callable():(\Generator<mixed, mixed, mixed, TReturn>|null) $gen
|
|
||||||
* @return Promise<TReturn>
|
|
||||||
*/
|
|
||||||
function call(callable $gen) : Promise {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TReturn
|
|
||||||
*/
|
|
||||||
class Promise {
|
|
||||||
/**
|
|
||||||
* @return TReturn
|
|
||||||
*/
|
|
||||||
function wait() {}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user