summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSatya Rohith <me@satyarohith.com>2021-06-21 12:13:35 +0530
committerGitHub <noreply@github.com>2021-06-21 16:43:35 +1000
commit952caa79b32e6c249977281ed494d4b1f98ed451 (patch)
tree3f072fc64b571eca1b203fb07df3689ad4fc3a95
parentbbc2745350687f209c6af75958cc12bb43e64042 (diff)
feat(lsp): quick fix actions to ignore lint errors (#10627)
Closes #10122
-rw-r--r--cli/lsp/analysis.rs115
-rw-r--r--cli/lsp/documents.rs12
-rw-r--r--cli/lsp/language_server.rs11
-rw-r--r--cli/tests/integration_tests_lsp.rs28
-rw-r--r--cli/tests/lsp/code_action_ignore_lint_params.json39
-rw-r--r--cli/tests/lsp/code_action_ignore_lint_response.json62
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"
+ }
+ ]
+ }
+ }
+ }
+]