1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00

Switch to amp v3

This commit is contained in:
Daniil Gentili 2023-07-19 10:51:22 +02:00
parent f2c77ca848
commit 8859e58464
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
11 changed files with 272 additions and 350 deletions

View File

@ -24,8 +24,8 @@
"ext-mbstring": "*",
"ext-tokenizer": "*",
"composer-runtime-api": "^2",
"amphp/amp": "^2.4.2",
"amphp/byte-stream": "^1.5",
"amphp/amp": "^3",
"amphp/byte-stream": "^2",
"composer/semver": "^1.4 || ^2.0 || ^3.0",
"composer/xdebug-handler": "^2.0 || ^3.0",
"dnoegel/php-xdg-base-dir": "^0.1.1",
@ -44,7 +44,7 @@
},
"require-dev": {
"ext-curl": "*",
"amphp/phpunit-util": "^2.0",
"amphp/phpunit-util": "^3",
"bamarni/composer-bin-plugin": "^1.4",
"brianium/paratest": "^6.9",
"mockery/mockery": "^1.5",

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer\Client;
use Amp\Promise;
use JsonMapper;
use Psalm\Internal\LanguageServer\ClientHandler;
use Psalm\Internal\LanguageServer\LanguageServer;
@ -42,11 +41,11 @@ class Workspace
* @param string $section The configuration section asked for.
* @param string|null $scopeUri The scope to get the configuration section for.
*/
public function requestConfiguration(string $section, ?string $scopeUri = null): Promise
public function requestConfiguration(string $section, ?string $scopeUri = null): object
{
$this->server->logDebug("workspace/configuration");
/** @var Promise<object> */
/** @var object */
return $this->handler->request('workspace/configuration', [
'items' => [
[

View File

@ -8,11 +8,7 @@ use AdvancedJsonRpc\Notification;
use AdvancedJsonRpc\Request;
use AdvancedJsonRpc\Response;
use AdvancedJsonRpc\SuccessResponse;
use Amp\Deferred;
use Amp\Promise;
use Generator;
use function Amp\call;
use Amp\DeferredFuture;
/**
* @internal
@ -37,24 +33,19 @@ class ClientHandler
*
* @param string $method The method to call
* @param array|object $params The method parameters
* @return Promise<mixed> Resolved with the result of the request or rejected with an error
* @return 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)
{
$id = $this->idGenerator->generate();
return call(
/**
* @return Generator<int, Promise, mixed, Promise<mixed>>
*/
function () use ($id, $method, $params): Generator {
yield $this->protocolWriter->write(
$this->protocolWriter->write(
new Message(
new Request($id, $method, (object) $params),
),
);
$deferred = new Deferred();
$deferred = new DeferredFuture();
$listener =
function (Message $msg) use ($id, $deferred, &$listener): void {
@ -69,17 +60,15 @@ class ClientHandler
// Received a response
$this->protocolReader->removeListener('message', $listener);
if (SuccessResponse::isSuccessResponse($msg->body)) {
$deferred->resolve($msg->body->result);
$deferred->complete($msg->body->result);
} else {
$deferred->fail($msg->body->error);
$deferred->error($msg->body->error);
}
}
};
$this->protocolReader->on('message', $listener);
return $deferred->promise();
},
);
return $deferred->getFuture()->await();
}
/**

View File

@ -11,10 +11,6 @@ use AdvancedJsonRpc\ErrorResponse;
use AdvancedJsonRpc\Request;
use AdvancedJsonRpc\Response;
use AdvancedJsonRpc\SuccessResponse;
use Amp\Loop;
use Amp\Promise;
use Amp\Success;
use Generator;
use InvalidArgumentException;
use JsonMapper;
use LanguageServerProtocol\ClientCapabilities;
@ -56,10 +52,9 @@ use Psalm\Internal\Provider\ParserCacheProvider;
use Psalm\Internal\Provider\ProjectCacheProvider;
use Psalm\Internal\Provider\Providers;
use Psalm\IssueBuffer;
use Revolt\EventLoop;
use Throwable;
use function Amp\asyncCoroutine;
use function Amp\call;
use function array_combine;
use function array_filter;
use function array_keys;
@ -170,64 +165,52 @@ class LanguageServer extends Dispatcher
);
$this->protocolReader->on(
'message',
asyncCoroutine(
function (Message $msg): void {
if (!$msg->body) {
return;
}
// Ignore responses, this is the handler for requests and notifications
if (Response::isResponse($msg->body)) {
return;
}
$result = null;
$error = null;
try {
// Invoke the method handler to get a result
$result = $this->dispatch($msg->body);
} catch (Error $e) {
// If a ResponseError is thrown, send it back in the Response
$error = $e;
} catch (Throwable $e) {
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
$error = new Error(
(string) $e,
ErrorCode::INTERNAL_ERROR,
null,
$e,
);
}
if ($error !== null) {
$this->logError($error->message);
}
// Only send a Response for a Request
// Notifications do not send Responses
/**
* @return Generator<int, Promise, mixed, void>
* @psalm-suppress UndefinedPropertyFetch
* @psalm-suppress MixedArgument
*/
function (Message $msg): Generator {
if (!$msg->body) {
return;
}
// Ignore responses, this is the handler for requests and notifications
if (Response::isResponse($msg->body)) {
return;
}
$result = null;
$error = null;
try {
// Invoke the method handler to get a result
/**
* @var Promise|null
*/
$dispatched = $this->dispatch($msg->body);
if ($dispatched !== null) {
$result = yield $dispatched;
} else {
$result = null;
}
} catch (Error $e) {
// If a ResponseError is thrown, send it back in the Response
$error = $e;
} catch (Throwable $e) {
// If an unexpected error occurred, send back an INTERNAL_ERROR error response
$error = new Error(
(string) $e,
ErrorCode::INTERNAL_ERROR,
null,
$e,
);
}
if (Request::isRequest($msg->body)) {
if ($error !== null) {
$this->logError($error->message);
$responseBody = new ErrorResponse($msg->body->id, $error);
} else {
$responseBody = new SuccessResponse($msg->body->id, $result);
}
// Only send a Response for a Request
// Notifications do not send Responses
/**
* @psalm-suppress UndefinedPropertyFetch
* @psalm-suppress MixedArgument
*/
if (Request::isRequest($msg->body)) {
if ($error !== null) {
$responseBody = new ErrorResponse($msg->body->id, $error);
} else {
$responseBody = new SuccessResponse($msg->body->id, $result);
}
yield $this->protocolWriter->write(new Message($responseBody));
}
},
),
$this->protocolWriter->write(new Message($responseBody));
}
},
),
);
$this->protocolReader->on(
@ -323,7 +306,7 @@ class LanguageServer extends Dispatcher
$clientConfiguration,
$progress,
);
Loop::run();
EventLoop::run();
} elseif ($clientConfiguration->TCPServerMode && $clientConfiguration->TCPServerAddress) {
// Run a TCP Server
$tcpServer = stream_socket_server('tcp://' . $clientConfiguration->TCPServerAddress, $errno, $errstr);
@ -346,7 +329,7 @@ class LanguageServer extends Dispatcher
$clientConfiguration,
$progress,
);
Loop::run();
EventLoop::run();
}
} else {
// Use STDIO
@ -359,7 +342,7 @@ class LanguageServer extends Dispatcher
$clientConfiguration,
$progress,
);
Loop::run();
EventLoop::run();
}
}
@ -377,7 +360,6 @@ class LanguageServer extends Dispatcher
* @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open.
* @param mixed $initializationOptions
* @param string|null $trace The initial trace setting. If omitted trace is disabled ('off').
* @psalm-return Promise<InitializeResult>
* @psalm-suppress PossiblyUnusedParam
*/
public function initialize(
@ -390,184 +372,174 @@ class LanguageServer extends Dispatcher
$initializationOptions = null,
?string $trace = null
//?array $workspaceFolders = null //error in json-dispatcher
): Promise {
): InitializeResult {
$this->clientInfo = $clientInfo;
$this->clientCapabilities = $capabilities;
$this->trace = $trace;
return call(
/** @return Generator<int, true, mixed, InitializeResult> */
function () {
$this->logInfo("Initializing...");
$this->clientStatus('initializing');
// Eventually, this might block on something. Leave it as a generator.
/** @psalm-suppress TypeDoesNotContainType */
if (false) {
yield true;
}
$this->logInfo("Initializing...");
$this->clientStatus('initializing');
$this->project_analyzer->serverMode($this);
$this->project_analyzer->serverMode($this);
$this->logInfo("Initializing: Getting code base...");
$this->clientStatus('initializing', 'getting code base');
$this->logInfo("Initializing: Getting code base...");
$this->clientStatus('initializing', 'getting code base');
$this->logInfo("Initializing: Scanning files ({$this->project_analyzer->threads} Threads)...");
$this->clientStatus('initializing', 'scanning files');
$this->codebase->scanFiles($this->project_analyzer->threads);
$this->logInfo("Initializing: Scanning files ({$this->project_analyzer->threads} Threads)...");
$this->clientStatus('initializing', 'scanning files');
$this->codebase->scanFiles($this->project_analyzer->threads);
$this->logInfo("Initializing: Registering stub files...");
$this->clientStatus('initializing', 'registering stub files');
$this->codebase->config->visitStubFiles($this->codebase, $this->project_analyzer->progress);
$this->logInfo("Initializing: Registering stub files...");
$this->clientStatus('initializing', 'registering stub files');
$this->codebase->config->visitStubFiles($this->codebase, $this->project_analyzer->progress);
if ($this->textDocument === null) {
$this->textDocument = new ServerTextDocument(
$this,
$this->codebase,
$this->project_analyzer,
);
}
if ($this->textDocument === null) {
$this->textDocument = new ServerTextDocument(
$this,
$this->codebase,
$this->project_analyzer,
);
}
if ($this->workspace === null) {
$this->workspace = new ServerWorkspace(
$this,
$this->codebase,
$this->project_analyzer,
);
}
if ($this->workspace === null) {
$this->workspace = new ServerWorkspace(
$this,
$this->codebase,
$this->project_analyzer,
);
}
$serverCapabilities = new ServerCapabilities();
$serverCapabilities = new ServerCapabilities();
//The server provides execute command support.
$serverCapabilities->executeCommandProvider = new ExecuteCommandOptions(['test']);
//The server provides execute command support.
$serverCapabilities->executeCommandProvider = new ExecuteCommandOptions(['test']);
$textDocumentSyncOptions = new TextDocumentSyncOptions();
$textDocumentSyncOptions = new TextDocumentSyncOptions();
//Open and close notifications are sent to the server.
$textDocumentSyncOptions->openClose = true;
//Open and close notifications are sent to the server.
$textDocumentSyncOptions->openClose = true;
$saveOptions = new SaveOptions();
//The client is supposed to include the content on save.
$saveOptions->includeText = true;
$textDocumentSyncOptions->save = $saveOptions;
$saveOptions = new SaveOptions();
//The client is supposed to include the content on save.
$saveOptions->includeText = true;
$textDocumentSyncOptions->save = $saveOptions;
/**
* Change notifications are sent to the server. See
* TextDocumentSyncKind.None, TextDocumentSyncKind.Full and
* TextDocumentSyncKind.Incremental. If omitted it defaults to
* TextDocumentSyncKind.None.
*/
if ($this->project_analyzer->onchange_line_limit === 0) {
/**
* Documents should not be synced at all.
*/
$textDocumentSyncOptions->change = TextDocumentSyncKind::NONE;
} else {
/**
* Documents are synced by always sending the full content
* of the document.
*/
$textDocumentSyncOptions->change = TextDocumentSyncKind::FULL;
}
/**
* Change notifications are sent to the server. See
* TextDocumentSyncKind.None, TextDocumentSyncKind.Full and
* TextDocumentSyncKind.Incremental. If omitted it defaults to
* TextDocumentSyncKind.None.
*/
if ($this->project_analyzer->onchange_line_limit === 0) {
/**
* Documents should not be synced at all.
*/
$textDocumentSyncOptions->change = TextDocumentSyncKind::NONE;
} else {
/**
* Documents are synced by always sending the full content
* of the document.
*/
$textDocumentSyncOptions->change = TextDocumentSyncKind::FULL;
}
/**
* Defines how text documents are synced. Is either a detailed structure
* defining each notification or for backwards compatibility the
* TextDocumentSyncKind number. If omitted it defaults to
* `TextDocumentSyncKind.None`.
*/
$serverCapabilities->textDocumentSync = $textDocumentSyncOptions;
/**
* Defines how text documents are synced. Is either a detailed structure
* defining each notification or for backwards compatibility the
* TextDocumentSyncKind number. If omitted it defaults to
* `TextDocumentSyncKind.None`.
*/
$serverCapabilities->textDocumentSync = $textDocumentSyncOptions;
/**
* The server provides document symbol support.
* Support "Find all symbols"
*/
$serverCapabilities->documentSymbolProvider = false;
/**
* The server provides workspace symbol support.
* Support "Find all symbols in workspace"
*/
$serverCapabilities->workspaceSymbolProvider = false;
/**
* The server provides goto definition support.
* Support "Go to definition"
*/
$serverCapabilities->definitionProvider = true;
/**
* The server provides find references support.
* Support "Find all references"
*/
$serverCapabilities->referencesProvider = false;
/**
* The server provides hover support.
* Support "Hover"
*/
$serverCapabilities->hoverProvider = true;
/**
* The server provides document symbol support.
* Support "Find all symbols"
*/
$serverCapabilities->documentSymbolProvider = false;
/**
* The server provides workspace symbol support.
* Support "Find all symbols in workspace"
*/
$serverCapabilities->workspaceSymbolProvider = false;
/**
* The server provides goto definition support.
* Support "Go to definition"
*/
$serverCapabilities->definitionProvider = true;
/**
* The server provides find references support.
* Support "Find all references"
*/
$serverCapabilities->referencesProvider = false;
/**
* The server provides hover support.
* Support "Hover"
*/
$serverCapabilities->hoverProvider = true;
/**
* The server provides completion support.
* Support "Completion"
*/
if ($this->project_analyzer->provide_completion) {
$serverCapabilities->completionProvider = new CompletionOptions();
/**
* The server provides support to resolve additional
* information for a completion item.
*/
$serverCapabilities->completionProvider->resolveProvider = false;
/**
* Most tools trigger completion request automatically without explicitly
* requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
* do so when the user starts to type an identifier. For example if the user
* types `c` in a JavaScript file code complete will automatically pop up
* present `console` besides others as a completion item. Characters that
* make up identifiers don't need to be listed here.
*
* If code complete should automatically be trigger on characters not being
* valid inside an identifier (for example `.` in JavaScript) list them in
* `triggerCharacters`.
*/
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>', ':',"[", "(", ",", " "];
}
/**
* The server provides completion support.
* Support "Completion"
*/
if ($this->project_analyzer->provide_completion) {
$serverCapabilities->completionProvider = new CompletionOptions();
/**
* The server provides support to resolve additional
* information for a completion item.
*/
$serverCapabilities->completionProvider->resolveProvider = false;
/**
* Most tools trigger completion request automatically without explicitly
* requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
* do so when the user starts to type an identifier. For example if the user
* types `c` in a JavaScript file code complete will automatically pop up
* present `console` besides others as a completion item. Characters that
* make up identifiers don't need to be listed here.
*
* If code complete should automatically be trigger on characters not being
* valid inside an identifier (for example `.` in JavaScript) list them in
* `triggerCharacters`.
*/
$serverCapabilities->completionProvider->triggerCharacters = ['$', '>', ':',"[", "(", ",", " "];
}
/**
* Whether code action supports the `data` property which is
* preserved between a `textDocument/codeAction` and a
* `codeAction/resolve` request.
*
* Support "Code Actions" if we support data
*
* @since LSP 3.16.0
*/
if ($this->clientCapabilities->textDocument->publishDiagnostics->dataSupport ?? false) {
$serverCapabilities->codeActionProvider = true;
}
/**
* Whether code action supports the `data` property which is
* preserved between a `textDocument/codeAction` and a
* `codeAction/resolve` request.
*
* Support "Code Actions" if we support data
*
* @since LSP 3.16.0
*/
if ($this->clientCapabilities->textDocument->publishDiagnostics->dataSupport ?? false) {
$serverCapabilities->codeActionProvider = true;
}
/**
* The server provides signature help support.
*/
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(['(', ',']);
/**
* The server provides signature help support.
*/
$serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(['(', ',']);
if ($this->client->clientConfiguration->baseline !== null) {
$this->logInfo('Utilizing Baseline: '.$this->client->clientConfiguration->baseline);
$this->issue_baseline= ErrorBaseline::read(
new FileProvider,
$this->client->clientConfiguration->baseline,
);
}
if ($this->client->clientConfiguration->baseline !== null) {
$this->logInfo('Utilizing Baseline: '.$this->client->clientConfiguration->baseline);
$this->issue_baseline= ErrorBaseline::read(
new FileProvider,
$this->client->clientConfiguration->baseline,
);
}
$this->logInfo("Initializing: Complete.");
$this->clientStatus('initialized');
$this->logInfo("Initializing: Complete.");
$this->clientStatus('initialized');
/**
* Information about the server.
*
* @since LSP 3.15.0
*/
$initializeResultServerInfo = new InitializeResultServerInfo('Psalm Language Server', PSALM_VERSION);
/**
* Information about the server.
*
* @since LSP 3.15.0
*/
$initializeResultServerInfo = new InitializeResultServerInfo('Psalm Language Server', PSALM_VERSION);
return new InitializeResult($serverCapabilities, $initializeResultServerInfo);
},
);
return new InitializeResult($serverCapabilities, $initializeResultServerInfo);
}
/**
@ -647,12 +619,12 @@ class LanguageServer extends Dispatcher
*/
public function doVersionedAnalysisDebounce(array $files, ?int $version = null): void
{
Loop::cancel($this->versionedAnalysisDelayToken);
EventLoop::cancel($this->versionedAnalysisDelayToken);
if ($this->client->clientConfiguration->onChangeDebounceMs === null) {
$this->doVersionedAnalysis($files, $version);
} else {
/** @psalm-suppress MixedAssignment,UnusedPsalmSuppress */
$this->versionedAnalysisDelayToken = Loop::delay(
$this->versionedAnalysisDelayToken = EventLoop::delay(
$this->client->clientConfiguration->onChangeDebounceMs,
fn() => $this->doVersionedAnalysis($files, $version),
);
@ -666,7 +638,7 @@ class LanguageServer extends Dispatcher
*/
public function doVersionedAnalysis(array $files, ?int $version = null): void
{
Loop::cancel($this->versionedAnalysisDelayToken);
EventLoop::cancel($this->versionedAnalysisDelayToken);
try {
$this->logDebug("Doing Analysis from version: $version");
$this->codebase->reloadFiles(
@ -819,7 +791,7 @@ class LanguageServer extends Dispatcher
* which they have sent a shutdown request. Clients should also wait with sending the exit notification until they
* have received a response from the shutdown request.
*/
public function shutdown(): Promise
public function shutdown(): void
{
$this->clientStatus('closing');
$this->logInfo("Shutting down...");
@ -830,7 +802,6 @@ class LanguageServer extends Dispatcher
$scanned_files,
);
$this->clientStatus('closed');
return new Success(null);
}
/**

View File

@ -5,12 +5,10 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer;
use AdvancedJsonRpc\Message as MessageBody;
use Amp\ByteStream\ResourceInputStream;
use Amp\Promise;
use Amp\ByteStream\ReadableResourceStream;
use Exception;
use Generator;
use Revolt\EventLoop;
use function Amp\asyncCall;
use function explode;
use function strlen;
use function substr;
@ -45,16 +43,11 @@ class ProtocolStreamReader implements ProtocolReader
*/
public function __construct($input)
{
$input = new ResourceInputStream($input);
asyncCall(
/**
* @return Generator<int, Promise<?string>, ?string, void>
*/
function () use ($input): Generator {
$input = new ReadableResourceStream($input);
EventLoop::queue(
function () use ($input): void {
while ($this->is_accepting_new_requests) {
$read_promise = $input->read();
$chunk = yield $read_promise;
$chunk = $input->read();
if ($chunk === null) {
break;

View File

@ -4,29 +4,28 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer;
use Amp\ByteStream\ResourceOutputStream;
use Amp\Promise;
use Amp\ByteStream\WritableResourceStream;
/**
* @internal
*/
class ProtocolStreamWriter implements ProtocolWriter
{
private ResourceOutputStream $output;
private WritableResourceStream $output;
/**
* @param resource $output
*/
public function __construct($output)
{
$this->output = new ResourceOutputStream($output);
$this->output = new WritableResourceStream($output);
}
/**
* {@inheritdoc}
*/
public function write(Message $msg): Promise
public function write(Message $msg): void
{
return $this->output->write((string)$msg);
$this->output->write((string)$msg);
}
}

View File

@ -4,14 +4,10 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer;
use Amp\Promise;
interface ProtocolWriter
{
/**
* Sends a Message to the client
*
* @return Promise Resolved when the message has been fully written out to the output stream
* Sends a Message to the client.
*/
public function write(Message $msg): Promise;
public function write(Message $msg): void;
}

View File

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer\Server;
use Amp\Promise;
use Amp\Success;
use LanguageServerProtocol\CodeAction;
use LanguageServerProtocol\CodeActionContext;
use LanguageServerProtocol\CodeActionKind;
@ -166,12 +164,11 @@ class TextDocument
*
* @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document
* @psalm-return Promise<Location>|Promise<null>
*/
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
public function definition(TextDocumentIdentifier $textDocument, Position $position): ?Location
{
if (!$this->server->client->clientConfiguration->provideDefinition) {
return new Success(null);
return null;
}
$this->server->logDebug(
@ -182,34 +179,32 @@ class TextDocument
//This currently doesnt work right with out of project files
if (!$this->codebase->config->isInProjectDirs($file_path)) {
return new Success(null);
return null;
}
try {
$reference = $this->codebase->getReferenceAtPositionAsReference($file_path, $position);
} catch (UnanalyzedFileException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
if ($reference === null) {
return new Success(null);
return null;
}
$code_location = $this->codebase->getSymbolLocationByReference($reference);
if (!$code_location) {
return new Success(null);
return null;
}
return new Success(
new Location(
LanguageServer::pathToUri($code_location->file_path),
new Range(
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1),
),
return new Location(
LanguageServer::pathToUri($code_location->file_path),
new Range(
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1),
),
);
}
@ -220,12 +215,11 @@ class TextDocument
*
* @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document
* @psalm-return Promise<Hover>|Promise<null>
*/
public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise
public function hover(TextDocumentIdentifier $textDocument, Position $position): ?Hover
{
if (!$this->server->client->clientConfiguration->provideHover) {
return new Success(null);
return null;
}
$this->server->logDebug(
@ -236,32 +230,32 @@ class TextDocument
//This currently doesnt work right with out of project files
if (!$this->codebase->config->isInProjectDirs($file_path)) {
return new Success(null);
return null;
}
try {
$reference = $this->codebase->getReferenceAtPositionAsReference($file_path, $position);
} catch (UnanalyzedFileException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
if ($reference === null) {
return new Success(null);
return null;
}
try {
$markup = $this->codebase->getMarkupContentForSymbolByReference($reference);
} catch (UnexpectedValueException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
if ($markup === null) {
return new Success(null);
return null;
}
return new Success(new Hover($markup, $reference->range));
return new Hover($markup, $reference->range);
}
/**
@ -276,12 +270,11 @@ class TextDocument
*
* @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position
* @psalm-return Promise<array<empty, empty>>|Promise<CompletionList>|Promise<null>
*/
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
public function completion(TextDocumentIdentifier $textDocument, Position $position): ?CompletionList
{
if (!$this->server->client->clientConfiguration->provideCompletion) {
return new Success(null);
return null;
}
$this->server->logDebug(
@ -292,7 +285,7 @@ class TextDocument
//This currently doesnt work right with out of project files
if (!$this->codebase->config->isInProjectDirs($file_path)) {
return new Success(null);
return null;
}
try {
@ -314,42 +307,42 @@ class TextDocument
$file_path,
);
}
return new Success(new CompletionList($completion_items, false));
return new CompletionList($completion_items, false);
}
} catch (UnanalyzedFileException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
} catch (TypeParseTreeException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
try {
$type_context = $this->codebase->getTypeContextAtPosition($file_path, $position);
if ($type_context) {
$completion_items = $this->codebase->getCompletionItemsForType($type_context);
return new Success(new CompletionList($completion_items, false));
return new CompletionList($completion_items, false);
}
} catch (UnexpectedValueException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
} catch (TypeParseTreeException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
$this->server->logError('completion not found at ' . $position->line . ':' . $position->character);
return new Success(null);
return null;
}
/**
* The signature help request is sent from the client to the server to request signature
* information at a given cursor position.
*/
public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise
public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): ?SignatureHelp
{
if (!$this->server->client->clientConfiguration->provideSignatureHelp) {
return new Success(null);
return null;
}
$this->server->logDebug(
@ -360,37 +353,35 @@ class TextDocument
//This currently doesnt work right with out of project files
if (!$this->codebase->config->isInProjectDirs($file_path)) {
return new Success(null);
return null;
}
try {
$argument_location = $this->codebase->getFunctionArgumentAtPosition($file_path, $position);
} catch (UnanalyzedFileException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
if ($argument_location === null) {
return new Success(null);
return null;
}
try {
$signature_information = $this->codebase->getSignatureInformation($argument_location[0], $file_path);
} catch (UnexpectedValueException $e) {
$this->server->logThrowable($e);
return new Success(null);
return null;
}
if (!$signature_information) {
return new Success(null);
return null;
}
return new Success(
new SignatureHelp(
[$signature_information],
0,
$argument_location[1],
),
return new SignatureHelp(
[$signature_information],
0,
$argument_location[1],
);
}
@ -401,10 +392,10 @@ class TextDocument
*
* @psalm-suppress PossiblyUnusedParam
*/
public function codeAction(TextDocumentIdentifier $textDocument, Range $range, CodeActionContext $context): Promise
public function codeAction(TextDocumentIdentifier $textDocument, Range $range, CodeActionContext $context): ?array
{
if (!$this->server->client->clientConfiguration->provideCodeActions) {
return new Success(null);
return null;
}
$this->server->logDebug(
@ -415,7 +406,7 @@ class TextDocument
//Don't report code actions for files we arent watching
if (!$this->codebase->config->isInProjectDirs($file_path)) {
return new Success(null);
return null;
}
$fixers = [];
@ -479,11 +470,9 @@ class TextDocument
}
if (empty($fixers)) {
return new Success(null);
return null;
}
return new Success(
array_values($fixers),
);
return array_values($fixers);
}
}

View File

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Psalm\Internal\LanguageServer\Server;
use Amp\Promise;
use Amp\Success;
use InvalidArgumentException;
use LanguageServerProtocol\FileChangeType;
use LanguageServerProtocol\FileEvent;
@ -126,7 +124,7 @@ class Workspace
* @param mixed $arguments
* @psalm-suppress PossiblyUnusedMethod
*/
public function executeCommand(string $command, $arguments): Promise
public function executeCommand(string $command, $arguments): void
{
$this->server->logDebug(
'workspace/executeCommand',
@ -155,7 +153,5 @@ class Workspace
$this->server->emitVersionedIssues([$file => $arguments['uri']]);
break;
}
return new Success(null);
}
}

View File

@ -2,7 +2,7 @@
namespace Psalm\Tests\LanguageServer;
use Amp\Deferred;
use Amp\DeferredFuture;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
@ -21,7 +21,6 @@ use Psalm\Tests\LanguageServer\Message as MessageBody;
use Psalm\Tests\LanguageServer\MockProtocolStream;
use Psalm\Tests\TestConfig;
use function Amp\Promise\wait;
use function rand;
class DiagnosticTest extends AsyncTestCase
@ -65,7 +64,7 @@ class DiagnosticTest extends AsyncTestCase
public function testSnippetSupportDisabled(): void
{
// Create a new promisor
$deferred = new Deferred;
$deferred = new DeferredFuture;
$this->setTimeout(5000);
$clientConfiguration = new ClientConfiguration();
@ -91,11 +90,11 @@ class DiagnosticTest extends AsyncTestCase
/** @psalm-suppress PossiblyNullPropertyFetch,UndefinedPropertyFetch,MixedPropertyFetch */
if ($message->body->method === 'telemetry/event' && $message->body->params->message === 'initialized') {
$this->assertFalse($server->clientCapabilities->textDocument->completion->completionItem->snippetSupport);
$deferred->resolve(null);
$deferred->complete(null);
}
});
wait($deferred->promise());
$deferred->getFuture()->await();
}
/**

View File

@ -4,14 +4,12 @@ declare(strict_types=1);
namespace Psalm\Tests\LanguageServer;
use Amp\Deferred;
use Amp\Loop;
use Amp\Promise;
use Psalm\Internal\LanguageServer\EmitterInterface;
use Psalm\Internal\LanguageServer\EmitterTrait;
use Psalm\Internal\LanguageServer\Message;
use Psalm\Internal\LanguageServer\ProtocolReader;
use Psalm\Internal\LanguageServer\ProtocolWriter;
use Revolt\EventLoop;
/**
* A fake duplex protocol stream
@ -24,17 +22,10 @@ class MockProtocolStream implements ProtocolReader, ProtocolWriter, EmitterInter
*
* @psalm-suppress PossiblyUnusedReturnValue
*/
public function write(Message $msg): Promise
public function write(Message $msg): void
{
Loop::defer(function () use ($msg): void {
EventLoop::queue(function () use ($msg): void {
$this->emit('message', [Message::parse((string)$msg)]);
});
// Create a new promisor
$deferred = new Deferred;
$deferred->resolve(null);
return $deferred->promise();
}
}