mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 12:58:54 -05:00
fix(lsp): support codeAction/resolve (#9405)
This commit is contained in:
parent
647f11b3ac
commit
b6353672f8
6 changed files with 215 additions and 119 deletions
|
@ -16,6 +16,7 @@ use deno_core::error::AnyError;
|
|||
use deno_core::futures::Future;
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_lint::rules;
|
||||
use lspower::lsp;
|
||||
|
@ -352,7 +353,7 @@ fn is_preferred(
|
|||
|
||||
/// Convert changes returned from a TypeScript quick fix action into edits
|
||||
/// for an LSP CodeAction.
|
||||
async fn ts_changes_to_edit<F, Fut, V>(
|
||||
pub async fn ts_changes_to_edit<F, Fut, V>(
|
||||
changes: &[tsc::FileTextChanges],
|
||||
index_provider: &F,
|
||||
version_provider: &V,
|
||||
|
@ -376,6 +377,13 @@ where
|
|||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionData {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub fix_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CodeActionCollection {
|
||||
actions: Vec<(lsp::CodeAction, tsc::CodeFixAction)>,
|
||||
|
@ -442,31 +450,16 @@ impl CodeActionCollection {
|
|||
|
||||
/// Add a TypeScript action to the actions as a "fix all" action, where it
|
||||
/// will fix all occurrences of the diagnostic in the file.
|
||||
pub async fn add_ts_fix_all_action<F, Fut, V>(
|
||||
pub fn add_ts_fix_all_action(
|
||||
&mut self,
|
||||
action: &tsc::CodeFixAction,
|
||||
specifier: &ModuleSpecifier,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
combined_code_actions: &tsc::CombinedCodeActions,
|
||||
index_provider: &F,
|
||||
version_provider: &V,
|
||||
) -> Result<(), AnyError>
|
||||
where
|
||||
F: Fn(ModuleSpecifier) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<LineIndex, AnyError>>,
|
||||
V: Fn(ModuleSpecifier) -> Option<i32>,
|
||||
{
|
||||
if combined_code_actions.commands.is_some() {
|
||||
return Err(custom_error(
|
||||
"UnsupportedFix",
|
||||
"The action returned from TypeScript is unsupported.",
|
||||
));
|
||||
}
|
||||
let edit = ts_changes_to_edit(
|
||||
&combined_code_actions.changes,
|
||||
index_provider,
|
||||
version_provider,
|
||||
)
|
||||
.await?;
|
||||
) {
|
||||
let data = Some(json!({
|
||||
"specifier": specifier,
|
||||
"fixId": action.fix_id,
|
||||
}));
|
||||
let title = if let Some(description) = &action.fix_all_description {
|
||||
description.clone()
|
||||
} else {
|
||||
|
@ -477,11 +470,11 @@ impl CodeActionCollection {
|
|||
title,
|
||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||
diagnostics: Some(vec![diagnostic.clone()]),
|
||||
edit,
|
||||
edit: None,
|
||||
command: None,
|
||||
is_preferred: None,
|
||||
disabled: None,
|
||||
data: None,
|
||||
data,
|
||||
};
|
||||
if let Some((existing, _)) =
|
||||
self.fix_all_actions.get(&action.fix_id.clone().unwrap())
|
||||
|
@ -493,7 +486,6 @@ impl CodeActionCollection {
|
|||
action.fix_id.clone().unwrap(),
|
||||
(code_action, action.clone()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move out the code actions and return them as a `CodeActionResponse`.
|
||||
|
|
|
@ -32,7 +32,7 @@ fn code_action_capabilities(
|
|||
.map_or(CodeActionProviderCapability::Simple(true), |_| {
|
||||
CodeActionProviderCapability::Options(CodeActionOptions {
|
||||
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
|
||||
resolve_provider: None,
|
||||
resolve_provider: Some(true),
|
||||
work_done_progress_options: Default::default(),
|
||||
})
|
||||
})
|
||||
|
|
|
@ -30,7 +30,9 @@ use crate::import_map::ImportMap;
|
|||
use crate::tsc_config::parse_config;
|
||||
use crate::tsc_config::TsConfig;
|
||||
|
||||
use super::analysis::ts_changes_to_edit;
|
||||
use super::analysis::CodeActionCollection;
|
||||
use super::analysis::CodeActionData;
|
||||
use super::analysis::CodeLensData;
|
||||
use super::analysis::CodeLensSource;
|
||||
use super::capabilities;
|
||||
|
@ -945,35 +947,7 @@ impl Inner {
|
|||
diagnostic,
|
||||
&file_diagnostics,
|
||||
) {
|
||||
let req = tsc::RequestMethod::GetCombinedCodeFix((
|
||||
specifier.clone(),
|
||||
json!(action.fix_id.clone().unwrap()),
|
||||
));
|
||||
let res =
|
||||
self.ts_server.request(self.snapshot(), req).await.map_err(
|
||||
|err| {
|
||||
error!("Unable to get combined fix from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
},
|
||||
)?;
|
||||
let combined_code_actions: tsc::CombinedCodeActions = from_value(res)
|
||||
.map_err(|err| {
|
||||
error!("Cannot decode combined actions from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
code_actions
|
||||
.add_ts_fix_all_action(
|
||||
&action,
|
||||
diagnostic,
|
||||
&combined_code_actions,
|
||||
&|s| self.get_line_index(s),
|
||||
&|s| self.documents.version(&s),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Unable to add fix all: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
code_actions.add_ts_fix_all_action(&action, &specifier, diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -983,6 +957,59 @@ impl Inner {
|
|||
Ok(Some(code_action_response))
|
||||
}
|
||||
|
||||
async fn code_action_resolve(
|
||||
&self,
|
||||
params: CodeAction,
|
||||
) -> LspResult<CodeAction> {
|
||||
let mark = self.performance.mark("code_action_resolve");
|
||||
let result =
|
||||
if let Some(data) = params.data.clone() {
|
||||
let code_action_data: CodeActionData =
|
||||
from_value(data).map_err(|err| {
|
||||
error!("Unable to decode code action data: {}", err);
|
||||
LspError::invalid_params("The CodeAction's data is invalid.")
|
||||
})?;
|
||||
let req = tsc::RequestMethod::GetCombinedCodeFix((
|
||||
code_action_data.specifier,
|
||||
json!(code_action_data.fix_id.clone()),
|
||||
));
|
||||
let res = self.ts_server.request(self.snapshot(), req).await.map_err(
|
||||
|err| {
|
||||
error!("Unable to get combined fix from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
},
|
||||
)?;
|
||||
let combined_code_actions: tsc::CombinedCodeActions =
|
||||
from_value(res).map_err(|err| {
|
||||
error!("Cannot decode combined actions from TypeScript: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
if combined_code_actions.commands.is_some() {
|
||||
error!("Deno does not support code actions with commands.");
|
||||
Err(LspError::invalid_request())
|
||||
} else {
|
||||
let mut code_action = params.clone();
|
||||
code_action.edit = ts_changes_to_edit(
|
||||
&combined_code_actions.changes,
|
||||
&|s| self.get_line_index(s),
|
||||
&|s| self.documents.version(&s),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Unable to convert changes to edits: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
Ok(code_action)
|
||||
}
|
||||
} else {
|
||||
Err(LspError::invalid_params(
|
||||
"The CodeAction's data is missing.",
|
||||
))
|
||||
};
|
||||
self.performance.measure(mark);
|
||||
result
|
||||
}
|
||||
|
||||
async fn code_lens(
|
||||
&mut self,
|
||||
params: CodeLensParams,
|
||||
|
@ -1610,6 +1637,13 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
self.0.lock().await.code_action(params).await
|
||||
}
|
||||
|
||||
async fn code_action_resolve(
|
||||
&self,
|
||||
params: CodeAction,
|
||||
) -> LspResult<CodeAction> {
|
||||
self.0.lock().await.code_action_resolve(params).await
|
||||
}
|
||||
|
||||
async fn code_lens(
|
||||
&self,
|
||||
params: CodeLensParams,
|
||||
|
@ -2319,6 +2353,13 @@ mod tests {
|
|||
"code_action_request.json",
|
||||
LspResponse::RequestFixture(2, "code_action_response.json".to_string()),
|
||||
),
|
||||
(
|
||||
"code_action_resolve_request.json",
|
||||
LspResponse::RequestFixture(
|
||||
4,
|
||||
"code_action_resolve_request_response.json".to_string(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"shutdown_request.json",
|
||||
LspResponse::Request(3, json!(null)),
|
||||
|
|
32
cli/tests/lsp/code_action_resolve_request.json
Normal file
32
cli/tests/lsp/code_action_resolve_request.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "codeAction/resolve",
|
||||
"params": {
|
||||
"title": "Add all missing 'async' modifiers",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"severity": 1,
|
||||
"code": 1308,
|
||||
"source": "deno-ts",
|
||||
"message": "'await' expressions are only allowed within async functions and at the top levels of modules.",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
}
|
||||
}
|
91
cli/tests/lsp/code_action_resolve_request_response.json
Normal file
91
cli/tests/lsp/code_action_resolve_request_response.json
Normal file
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"title": "Add all missing 'async' modifiers",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"severity": 1,
|
||||
"code": 1308,
|
||||
"source": "deno-ts",
|
||||
"message": "'await' expressions are only allowed within async functions and at the top levels of modules.",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"documentChanges": [
|
||||
{
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"version": 1
|
||||
},
|
||||
"edits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 7
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"newText": "async "
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 25
|
||||
}
|
||||
},
|
||||
"newText": "Promise<void>"
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 7
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"newText": "async "
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 25
|
||||
}
|
||||
},
|
||||
"newText": "Promise<void>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
}
|
|
@ -82,69 +82,9 @@
|
|||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"documentChanges": [
|
||||
{
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"version": 1
|
||||
},
|
||||
"edits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 7
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"newText": "async "
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 25
|
||||
}
|
||||
},
|
||||
"newText": "Promise<void>"
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 7
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"newText": "async "
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 25
|
||||
}
|
||||
},
|
||||
"newText": "Promise<void>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue