mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat(lsp): quick fix actions to ignore lint errors (#10627)
Closes #10122
This commit is contained in:
parent
bbc2745350
commit
952caa79b3
6 changed files with 266 additions and 1 deletions
|
@ -5,6 +5,7 @@ use super::tsc;
|
|||
|
||||
use crate::ast;
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::lsp::documents::DocumentData;
|
||||
use crate::media_type::MediaType;
|
||||
use crate::module_graph::parse_deno_types;
|
||||
use crate::module_graph::parse_ts_reference;
|
||||
|
@ -619,6 +620,7 @@ pub struct DenoFixData {
|
|||
#[derive(Debug, Clone)]
|
||||
enum CodeActionKind {
|
||||
Deno(lsp::CodeAction),
|
||||
DenoLint(lsp::CodeAction),
|
||||
Tsc(lsp::CodeAction, tsc::CodeFixAction),
|
||||
}
|
||||
|
||||
|
@ -665,6 +667,106 @@ impl CodeActionCollection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn add_deno_lint_ignore_action(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
document: Option<&DocumentData>,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
) -> Result<(), AnyError> {
|
||||
let code = diagnostic
|
||||
.code
|
||||
.as_ref()
|
||||
.map(|v| match v {
|
||||
lsp::NumberOrString::String(v) => v.to_owned(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let line_content = if let Some(doc) = document {
|
||||
doc
|
||||
.content_line(diagnostic.range.start.line as usize)
|
||||
.ok()
|
||||
.flatten()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
specifier.clone(),
|
||||
vec![lsp::TextEdit {
|
||||
new_text: prepend_whitespace(
|
||||
format!("// deno-lint-ignore {}\n", code),
|
||||
line_content,
|
||||
),
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: diagnostic.range.start.line,
|
||||
character: 0,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: diagnostic.range.start.line,
|
||||
character: 0,
|
||||
},
|
||||
},
|
||||
}],
|
||||
);
|
||||
let ignore_error_action = lsp::CodeAction {
|
||||
title: format!("Disable {} for this line", code),
|
||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||
diagnostics: Some(vec![diagnostic.clone()]),
|
||||
command: None,
|
||||
is_preferred: None,
|
||||
disabled: None,
|
||||
data: None,
|
||||
edit: Some(lsp::WorkspaceEdit {
|
||||
changes: Some(changes),
|
||||
change_annotations: None,
|
||||
document_changes: None,
|
||||
}),
|
||||
};
|
||||
self
|
||||
.actions
|
||||
.push(CodeActionKind::DenoLint(ignore_error_action));
|
||||
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
specifier.clone(),
|
||||
vec![lsp::TextEdit {
|
||||
new_text: "// deno-lint-ignore-file\n".to_string(),
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
},
|
||||
}],
|
||||
);
|
||||
let ignore_file_action = lsp::CodeAction {
|
||||
title: "Ignore lint errors for the entire file".to_string(),
|
||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||
diagnostics: Some(vec![diagnostic.clone()]),
|
||||
command: None,
|
||||
is_preferred: None,
|
||||
disabled: None,
|
||||
data: None,
|
||||
edit: Some(lsp::WorkspaceEdit {
|
||||
changes: Some(changes),
|
||||
change_annotations: None,
|
||||
document_changes: None,
|
||||
}),
|
||||
};
|
||||
self
|
||||
.actions
|
||||
.push(CodeActionKind::DenoLint(ignore_file_action));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a TypeScript code fix action to the code actions collection.
|
||||
pub(crate) async fn add_ts_fix_action(
|
||||
&mut self,
|
||||
|
@ -779,6 +881,7 @@ impl CodeActionCollection {
|
|||
.map(|i| match i {
|
||||
CodeActionKind::Tsc(c, _) => lsp::CodeActionOrCommand::CodeAction(c),
|
||||
CodeActionKind::Deno(c) => lsp::CodeActionOrCommand::CodeAction(c),
|
||||
CodeActionKind::DenoLint(c) => lsp::CodeActionOrCommand::CodeAction(c),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -833,6 +936,18 @@ impl CodeActionCollection {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prepend the whitespace characters found at the start of line_content to content.
|
||||
fn prepend_whitespace(content: String, line_content: Option<String>) -> String {
|
||||
if let Some(line) = line_content {
|
||||
let whitespaces =
|
||||
line.chars().position(|c| !c.is_whitespace()).unwrap_or(0);
|
||||
let whitespace = &line[0..whitespaces];
|
||||
format!("{}{}", &whitespace, content)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -139,12 +139,22 @@ impl DocumentData {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_line(&self, line: usize) -> Result<Option<String>, AnyError> {
|
||||
let content = self.content().ok().flatten();
|
||||
if let Some(content) = content {
|
||||
let lines = content.lines().into_iter().collect::<Vec<&str>>();
|
||||
Ok(Some(lines[line].to_string()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DocumentCache {
|
||||
dependents_graph: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>,
|
||||
docs: HashMap<ModuleSpecifier, DocumentData>,
|
||||
pub docs: HashMap<ModuleSpecifier, DocumentData>,
|
||||
}
|
||||
|
||||
impl DocumentCache {
|
||||
|
|
|
@ -976,6 +976,7 @@ impl Inner {
|
|||
}
|
||||
_ => false,
|
||||
},
|
||||
"deno-lint" => matches!(&d.code, Some(_)),
|
||||
"deno" => match &d.code {
|
||||
Some(NumberOrString::String(code)) => {
|
||||
code == "no-cache" || code == "no-cache-data"
|
||||
|
@ -1049,6 +1050,16 @@ impl Inner {
|
|||
LspError::internal_error()
|
||||
})?
|
||||
}
|
||||
Some("deno-lint") => code_actions
|
||||
.add_deno_lint_ignore_action(
|
||||
&specifier,
|
||||
self.documents.docs.get(&specifier),
|
||||
diagnostic,
|
||||
)
|
||||
.map_err(|err| {
|
||||
error!("Unable to fix lint error: {}", err);
|
||||
LspError::internal_error()
|
||||
})?,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2734,3 +2734,31 @@ fn lsp_configuration_did_change() {
|
|||
);
|
||||
shutdown(&mut client);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_code_actions_ignore_lint() {
|
||||
let mut client = init("initialize_params.json");
|
||||
did_open(
|
||||
&mut client,
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "let message = 'Hello, Deno!';\nconsole.log(message);\n"
|
||||
}
|
||||
}),
|
||||
);
|
||||
let (maybe_res, maybe_err) = client
|
||||
.write_request(
|
||||
"textDocument/codeAction",
|
||||
load_fixture("code_action_ignore_lint_params.json"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(maybe_err.is_none());
|
||||
assert_eq!(
|
||||
maybe_res,
|
||||
Some(load_fixture("code_action_ignore_lint_response.json"))
|
||||
);
|
||||
shutdown(&mut client);
|
||||
}
|
||||
|
|
39
cli/tests/lsp/code_action_ignore_lint_params.json
Normal file
39
cli/tests/lsp/code_action_ignore_lint_params.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
},
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 5
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 12
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 5
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 12
|
||||
}
|
||||
},
|
||||
"severity": 1,
|
||||
"code": "prefer-const",
|
||||
"source": "deno-lint",
|
||||
"message": "'message' is never reassigned\nUse 'const' instead",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"only": [
|
||||
"quickfix"
|
||||
]
|
||||
}
|
||||
}
|
62
cli/tests/lsp/code_action_ignore_lint_response.json
Normal file
62
cli/tests/lsp/code_action_ignore_lint_response.json
Normal file
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"title": "Disable prefer-const for this line",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 1, "character": 5 },
|
||||
"end": { "line": 1, "character": 12 }
|
||||
},
|
||||
"severity": 1,
|
||||
"code": "prefer-const",
|
||||
"source": "deno-lint",
|
||||
"message": "'message' is never reassigned\nUse 'const' instead",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"changes": {
|
||||
"file:///a/file.ts": [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 1, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
},
|
||||
"newText": "// deno-lint-ignore prefer-const\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Ignore lint errors for the entire file",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 1, "character": 5 },
|
||||
"end": { "line": 1, "character": 12 }
|
||||
},
|
||||
"severity": 1,
|
||||
"code": "prefer-const",
|
||||
"source": "deno-lint",
|
||||
"message": "'message' is never reassigned\nUse 'const' instead",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"changes": {
|
||||
"file:///a/file.ts": [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 0, "character": 0 }
|
||||
},
|
||||
"newText": "// deno-lint-ignore-file\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
Loading…
Add table
Reference in a new issue