diff options
author | Satya Rohith <me@satyarohith.com> | 2021-06-21 12:13:35 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-21 16:43:35 +1000 |
commit | 952caa79b32e6c249977281ed494d4b1f98ed451 (patch) | |
tree | 3f072fc64b571eca1b203fb07df3689ad4fc3a95 | |
parent | bbc2745350687f209c6af75958cc12bb43e64042 (diff) |
feat(lsp): quick fix actions to ignore lint errors (#10627)
Closes #10122
-rw-r--r-- | cli/lsp/analysis.rs | 115 | ||||
-rw-r--r-- | cli/lsp/documents.rs | 12 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 11 | ||||
-rw-r--r-- | cli/tests/integration_tests_lsp.rs | 28 | ||||
-rw-r--r-- | cli/tests/lsp/code_action_ignore_lint_params.json | 39 | ||||
-rw-r--r-- | cli/tests/lsp/code_action_ignore_lint_response.json | 62 |
6 files changed, 266 insertions, 1 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 2235a2c83..f3af5fc8d 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -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::*; diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 5e08245cb..45903fa21 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -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 { diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 5a016d5c3..491e402ad 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -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() + })?, _ => (), } } diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs index 1f58c0749..afc93764a 100644 --- a/cli/tests/integration_tests_lsp.rs +++ b/cli/tests/integration_tests_lsp.rs @@ -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); +} diff --git a/cli/tests/lsp/code_action_ignore_lint_params.json b/cli/tests/lsp/code_action_ignore_lint_params.json new file mode 100644 index 000000000..7711812fd --- /dev/null +++ b/cli/tests/lsp/code_action_ignore_lint_params.json @@ -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" + ] + } +} diff --git a/cli/tests/lsp/code_action_ignore_lint_response.json b/cli/tests/lsp/code_action_ignore_lint_response.json new file mode 100644 index 000000000..f5c24ec21 --- /dev/null +++ b/cli/tests/lsp/code_action_ignore_lint_response.json @@ -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" + } + ] + } + } + } +] |