From bb7ee4f4450882419ac46378df882d243eb150da Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Tue, 14 Sep 2021 17:46:51 +0530 Subject: [PATCH] feat(lsp): ignore specific lint for entire file (#12023) --- cli/lsp/analysis.rs | 74 ++++++++++++++- cli/tests/integration/lsp_tests.rs | 34 +++++++ .../lsp/code_action_ignore_lint_response.json | 30 ++++++ ...code_action_update_ignore_lint_params.json | 39 ++++++++ ...de_action_update_ignore_lint_response.json | 92 +++++++++++++++++++ 5 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 cli/tests/testdata/lsp/code_action_update_ignore_lint_params.json create mode 100644 cli/tests/testdata/lsp/code_action_update_ignore_lint_response.json diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 768ef26517..d1cf14500e 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -707,9 +707,10 @@ impl CodeActionCollection { }) .unwrap(); - let line_content = document.map(|d| { - d.source() - .text_info() + let document_source = document.map(|d| d.source()); + + let line_content = document_source.map(|d| { + d.text_info() .line_text(diagnostic.range.start.line as usize) .to_string() }); @@ -752,6 +753,73 @@ impl CodeActionCollection { .actions .push(CodeActionKind::DenoLint(ignore_error_action)); + // Disable a lint error for the entire file. + let parsed_source = + document_source.and_then(|d| d.module().and_then(|r| r.as_ref().ok())); + let maybe_ignore_comment = parsed_source.and_then(|ps| { + // Note: we can use ps.get_leading_comments() but it doesn't + // work when shebang is present at the top of the file. + ps.comments().get_vec().iter().find_map(|c| { + let comment_text = c.text.trim(); + comment_text.split_whitespace().next().and_then(|prefix| { + if prefix == "deno-lint-ignore-file" { + Some(c.clone()) + } else { + None + } + }) + }) + }); + + let mut new_text = format!("// deno-lint-ignore-file {}\n", code); + let mut range = lsp::Range { + start: lsp::Position { + line: 0, + character: 0, + }, + end: lsp::Position { + line: 0, + character: 0, + }, + }; + // If ignore file comment already exists, append the lint code + // to the existing comment. + if let Some(ignore_comment) = maybe_ignore_comment { + new_text = format!(" {}", code); + // Get the end position of the comment. + let line = parsed_source + .unwrap() + .source() + .line_and_column_index(ignore_comment.span.hi()); + let position = lsp::Position { + line: line.line_index as u32, + character: line.column_index as u32, + }; + // Set the edit range to the end of the comment. + range.start = position; + range.end = position; + } + + let mut changes = HashMap::new(); + changes.insert(specifier.clone(), vec![lsp::TextEdit { new_text, range }]); + let ignore_file_action = lsp::CodeAction { + title: format!("Disable {} for the entire file", 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_file_action)); + let mut changes = HashMap::new(); changes.insert( specifier.clone(), diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 3d87c222d7..5034b6ed63 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -3526,3 +3526,37 @@ fn lsp_code_actions_ignore_lint() { ); shutdown(&mut client); } + +/// This test exercises updating an existing deno-lint-ignore-file comment. +#[test] +fn lsp_code_actions_update_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": +"#!/usr/bin/env -S deno run +// deno-lint-ignore-file camelcase +let snake_case = 'Hello, Deno!'; +console.log(snake_case); +", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_update_ignore_lint_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_update_ignore_lint_response.json")) + ); + shutdown(&mut client); +} diff --git a/cli/tests/testdata/lsp/code_action_ignore_lint_response.json b/cli/tests/testdata/lsp/code_action_ignore_lint_response.json index f5c24ec21b..d15fccca96 100644 --- a/cli/tests/testdata/lsp/code_action_ignore_lint_response.json +++ b/cli/tests/testdata/lsp/code_action_ignore_lint_response.json @@ -29,6 +29,36 @@ } } }, + { + "title": "Disable prefer-const 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 prefer-const\n" + } + ] + } + } + }, { "title": "Ignore lint errors for the entire file", "kind": "quickfix", diff --git a/cli/tests/testdata/lsp/code_action_update_ignore_lint_params.json b/cli/tests/testdata/lsp/code_action_update_ignore_lint_params.json new file mode 100644 index 0000000000..8cbf1185f4 --- /dev/null +++ b/cli/tests/testdata/lsp/code_action_update_ignore_lint_params.json @@ -0,0 +1,39 @@ +{ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 3, + "character": 5 + }, + "end": { + "line": 3, + "character": 15 + } + }, + "context": { + "diagnostics": [ + { + "range": { + "start": { + "line": 3, + "character": 5 + }, + "end": { + "line": 3, + "character": 15 + } + }, + "severity": 1, + "code": "prefer-const", + "source": "deno-lint", + "message": "'snake_case' is never reassigned\nUse 'const' instead", + "relatedInformation": [] + } + ], + "only": [ + "quickfix" + ] + } +} diff --git a/cli/tests/testdata/lsp/code_action_update_ignore_lint_response.json b/cli/tests/testdata/lsp/code_action_update_ignore_lint_response.json new file mode 100644 index 0000000000..a23e719e0c --- /dev/null +++ b/cli/tests/testdata/lsp/code_action_update_ignore_lint_response.json @@ -0,0 +1,92 @@ +[ + { + "title": "Disable prefer-const for this line", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { "line": 3, "character": 5 }, + "end": { "line": 3, "character": 15 } + }, + "severity": 1, + "code": "prefer-const", + "source": "deno-lint", + "message": "'snake_case' is never reassigned\nUse 'const' instead", + "relatedInformation": [] + } + ], + "edit": { + "changes": { + "file:///a/file.ts": [ + { + "range": { + "start": { "line": 3, "character": 0 }, + "end": { "line": 3, "character": 0 } + }, + "newText": "// deno-lint-ignore prefer-const\n" + } + ] + } + } + }, + { + "title": "Disable prefer-const for the entire file", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { "line": 3, "character": 5 }, + "end": { "line": 3, "character": 15 } + }, + "severity": 1, + "code": "prefer-const", + "source": "deno-lint", + "message": "'snake_case' is never reassigned\nUse 'const' instead", + "relatedInformation": [] + } + ], + "edit": { + "changes": { + "file:///a/file.ts": [ + { + "range": { + "start": { "line": 1, "character": 34 }, + "end": { "line": 1, "character": 34 } + }, + "newText": " prefer-const" + } + ] + } + } + }, + { + "title": "Ignore lint errors for the entire file", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { "line": 3, "character": 5 }, + "end": { "line": 3, "character": 15 } + }, + "severity": 1, + "code": "prefer-const", + "source": "deno-lint", + "message": "'snake_case' 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" + } + ] + } + } + } +]