From 6d14326375b22e4867491d6073d1b3cf79e0eb42 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 30 Dec 2021 15:11:33 -0800 Subject: [PATCH 1/5] Feat: Initial Support for Code Actions --- .../LanguageServer/LanguageServer.php | 6 +- .../LanguageServer/Server/TextDocument.php | 94 +++++++++++++++++-- .../LanguageServer/Server/Workspace.php | 13 ++- 3 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/Psalm/Internal/LanguageServer/LanguageServer.php b/src/Psalm/Internal/LanguageServer/LanguageServer.php index f6525aacea6..6716f2024ac 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageServer.php +++ b/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -238,7 +238,7 @@ function () { $this->textDocument = new ServerTextDocument( $this, $codebase, - $this->project_analyzer->onchange_line_limit + $this->project_analyzer ); } @@ -246,7 +246,7 @@ function () { $this->workspace = new ServerWorkspace( $this, $codebase, - $this->project_analyzer->onchange_line_limit + $this->project_analyzer ); } @@ -279,6 +279,8 @@ function () { // Support "Hover" $serverCapabilities->hoverProvider = true; // Support "Completion" + $serverCapabilities->codeActionProvider = true; + // Support "Code Actions" if ($this->project_analyzer->provide_completion) { $serverCapabilities->completionProvider = new CompletionOptions(); diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index 4f2f11865ad..a584341fcf7 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -17,14 +17,20 @@ use LanguageServerProtocol\TextDocumentContentChangeEvent; use LanguageServerProtocol\TextDocumentIdentifier; use LanguageServerProtocol\TextDocumentItem; +use LanguageServerProtocol\TextEdit; use LanguageServerProtocol\VersionedTextDocumentIdentifier; +use LanguageServerProtocol\WorkspaceEdit; use Psalm\Codebase; use Psalm\Exception\UnanalyzedFileException; +use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\LanguageServer\LanguageServer; +use Psalm\IssueBuffer; use UnexpectedValueException; +use function array_combine; use function count; use function error_log; +use function preg_match; use function substr_count; /** @@ -42,17 +48,19 @@ class TextDocument */ protected $codebase; - /** @var ?int */ - protected $onchange_line_limit; + /** + * @var ProjectAnalyzer + */ + protected $project_analyzer; public function __construct( LanguageServer $server, Codebase $codebase, - ?int $onchange_line_limit + ProjectAnalyzer $project_analyzer ) { $this->server = $server; $this->codebase = $codebase; - $this->onchange_line_limit = $onchange_line_limit; + $this->project_analyzer = $project_analyzer; } /** @@ -116,7 +124,7 @@ public function didChange(VersionedTextDocumentIdentifier $textDocument, array $ return; } - if ($this->onchange_line_limit === 0) { + if ($this->project_analyzer->onchange_line_limit === 0) { return; } @@ -126,8 +134,8 @@ public function didChange(VersionedTextDocumentIdentifier $textDocument, array $ throw new UnexpectedValueException('Not expecting partial diff'); } - if ($this->onchange_line_limit !== null) { - if (substr_count($new_content, "\n") > $this->onchange_line_limit) { + if ($this->project_analyzer->onchange_line_limit !== null) { + if (substr_count($new_content, "\n") > $this->project_analyzer->onchange_line_limit) { return; } } @@ -336,4 +344,76 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po $signature_information, ], 0, $argument_location[1])); } + + /** + * The code action request is sent from the client to the server to compute commands + * for a given text document and range. These commands are typically code fixes to + * either fix problems or to beautify/refactor code. + */ + public function codeAction(TextDocumentIdentifier $textDocument, Range $range, $context): Promise + { + $file_path = LanguageServer::uriToPath($textDocument->uri); + if (!$this->codebase->file_provider->isOpen($file_path)) { + return new Success(null); + } + + $all_file_paths_to_analyze = [$file_path]; + $this->codebase->analyzer->addFilesToAnalyze( + array_combine($all_file_paths_to_analyze, $all_file_paths_to_analyze) + ); + $this->codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); + + $issues = IssueBuffer::clear(); + + if (empty($issues[$file_path])) { + return new Success(null); + } + + $file_contents = $this->codebase->getFileContents($file_path); + + $offsetStart = $range->start->toOffset($file_contents); + $offsetEnd = $range->end->toOffset($file_contents); + + $fixers = []; + foreach ($issues[$file_path] as $issue) { + if ($offsetStart === $issue->from && $offsetEnd === $issue->to) { + $snippetRange = new Range( + new Position($issue->line_from-1), + new Position($issue->line_to) + ); + + $indentation = ''; + if (preg_match('/^(\s*)/', $issue->snippet, $indentation)) { + $indentation = $indentation[1]; + } + + //Suppress Ability + $fixers[] = [ + 'title' => "Suppress {$issue->type} for this line", + 'kind' => 'quickfix', + 'edit' => new WorkspaceEdit( + [ + $textDocument->uri => [ + new TextEdit( + $snippetRange, + "{$indentation}/**\n". + "{$indentation} * @psalm-suppress {$issue->type}\n". + "{$indentation} */\n". + "{$issue->snippet}\n" + ) + ] + ] + ) + ]; + } + } + + if (empty($fixers)) { + return new Success(null); + } + + return new Success( + $fixers + ); + } } diff --git a/src/Psalm/Internal/LanguageServer/Server/Workspace.php b/src/Psalm/Internal/LanguageServer/Server/Workspace.php index 3d51d005c95..c1bc2bd7073 100644 --- a/src/Psalm/Internal/LanguageServer/Server/Workspace.php +++ b/src/Psalm/Internal/LanguageServer/Server/Workspace.php @@ -7,6 +7,7 @@ use LanguageServerProtocol\FileChangeType; use LanguageServerProtocol\FileEvent; use Psalm\Codebase; +use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\LanguageServer\LanguageServer; /** @@ -24,17 +25,19 @@ class Workspace */ protected $codebase; - /** @var ?int */ - protected $onchange_line_limit; + /** + * @var ProjectAnalyzer + */ + protected $project_analyzer; public function __construct( LanguageServer $server, Codebase $codebase, - ?int $onchange_line_limit + ProjectAnalyzer $project_analyzer ) { $this->server = $server; $this->codebase = $codebase; - $this->onchange_line_limit = $onchange_line_limit; + $this->project_analyzer = $project_analyzer; } /** @@ -62,7 +65,7 @@ public function didChangeWatchedFiles(array $changes): void continue; } - if ($this->onchange_line_limit === 0) { + if ($this->project_analyzer->onchange_line_limit === 0) { continue; } From 6c024b1d96e4b66e7886c0b392cf8cf5425b33dc Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 30 Dec 2021 15:13:01 -0800 Subject: [PATCH 2/5] fix spaces --- src/Psalm/Internal/LanguageServer/Server/TextDocument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index a584341fcf7..e6d6da6d7b1 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -345,7 +345,7 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po ], 0, $argument_location[1])); } - /** + /** * The code action request is sent from the client to the server to compute commands * for a given text document and range. These commands are typically code fixes to * either fix problems or to beautify/refactor code. From b2823ca03412e3a8ad5993e8bcc5f5ad093e6765 Mon Sep 17 00:00:00 2001 From: Andrew Nagy Date: Thu, 30 Dec 2021 23:45:11 +0000 Subject: [PATCH 3/5] fix psalm issues --- .../LanguageServer/Server/TextDocument.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index e6d6da6d7b1..21df0b8a45a 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -349,8 +349,9 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po * The code action request is sent from the client to the server to compute commands * for a given text document and range. These commands are typically code fixes to * either fix problems or to beautify/refactor code. + * */ - public function codeAction(TextDocumentIdentifier $textDocument, Range $range, $context): Promise + public function codeAction(TextDocumentIdentifier $textDocument, Range $range): Promise { $file_path = LanguageServer::uriToPath($textDocument->uri); if (!$this->codebase->file_provider->isOpen($file_path)) { @@ -383,14 +384,21 @@ public function codeAction(TextDocumentIdentifier $textDocument, Range $range, $ ); $indentation = ''; - if (preg_match('/^(\s*)/', $issue->snippet, $indentation)) { - $indentation = $indentation[1]; + if (preg_match('/^(\s*)/', $issue->snippet, $matches)) { + $indentation = $matches[1] ?? ''; } //Suppress Ability $fixers[] = [ 'title' => "Suppress {$issue->type} for this line", 'kind' => 'quickfix', + /** + * There are bugs in how LanguageServer is declared https://github.com/felixfbecker/php-language-server-protocol + * + * See: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspaceEdit + * + * @psalm-suppress InvalidArgument + */ 'edit' => new WorkspaceEdit( [ $textDocument->uri => [ From f8f8f6bc41f0fe392a65e70281dee24d81634342 Mon Sep 17 00:00:00 2001 From: Andrew Nagy Date: Fri, 31 Dec 2021 23:11:55 +0000 Subject: [PATCH 4/5] fix SA and lint issues, prevent duplicate suppressions --- .../LanguageServer/Server/TextDocument.php | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index 21df0b8a45a..24ef07fc3ac 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -388,30 +388,35 @@ public function codeAction(TextDocumentIdentifier $textDocument, Range $range): $indentation = $matches[1] ?? ''; } + + /** + * Suppress Psalm because ther are bugs in how + * LanguageServer's signature of WorkspaceEdit is declared: + * + * See: + * https://github.com/felixfbecker/php-language-server-protocol + * See: + * https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspaceEdit + * + * @psalm-suppress InvalidArgument + */ + $edit = new WorkspaceEdit([ + $textDocument->uri => [ + new TextEdit( + $snippetRange, + "{$indentation}/**\n". + "{$indentation} * @psalm-suppress {$issue->type}\n". + "{$indentation} */\n". + "{$issue->snippet}\n" + ) + ] + ]); + //Suppress Ability - $fixers[] = [ + $fixers["suppress.{$issue->type}"] = [ 'title' => "Suppress {$issue->type} for this line", 'kind' => 'quickfix', - /** - * There are bugs in how LanguageServer is declared https://github.com/felixfbecker/php-language-server-protocol - * - * See: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspaceEdit - * - * @psalm-suppress InvalidArgument - */ - 'edit' => new WorkspaceEdit( - [ - $textDocument->uri => [ - new TextEdit( - $snippetRange, - "{$indentation}/**\n". - "{$indentation} * @psalm-suppress {$issue->type}\n". - "{$indentation} */\n". - "{$issue->snippet}\n" - ) - ] - ] - ) + 'edit' => $edit ]; } } @@ -421,7 +426,7 @@ public function codeAction(TextDocumentIdentifier $textDocument, Range $range): } return new Success( - $fixers + array_values($fixers) ); } } From e3116e0ab565cc152304f36e3cc025e7292e32ec Mon Sep 17 00:00:00 2001 From: Andrew Nagy Date: Fri, 31 Dec 2021 23:17:27 +0000 Subject: [PATCH 5/5] dont allow fallback functions --- src/Psalm/Internal/LanguageServer/Server/TextDocument.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index 24ef07fc3ac..2a1a82349ac 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -28,6 +28,7 @@ use UnexpectedValueException; use function array_combine; +use function array_values; use function count; use function error_log; use function preg_match;