1
0
Fork 0
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:
Kitson Kelly 2021-02-06 07:10:53 +11:00 committed by GitHub
parent 647f11b3ac
commit b6353672f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 119 deletions

View file

@ -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`.

View file

@ -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(),
})
})

View file

@ -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)),

View 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"
}
}
}

View 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"
}
}

View file

@ -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"
}
}
]