diff options
-rw-r--r-- | cli/lsp/analysis.rs | 22 | ||||
-rw-r--r-- | cli/tests/integration/lsp_tests.rs | 198 |
2 files changed, 165 insertions, 55 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index ec279022d..808f09dec 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -848,8 +848,26 @@ impl CodeActionCollection { /// Move out the code actions and return them as a `CodeActionResponse`. pub fn get_response(self) -> lsp::CodeActionResponse { - self - .actions + let mut actions = self.actions; + + // Prefer TSC fixes first, then Deno fixes, then Deno lint fixes. + actions.sort_by(|a, b| match (a, b) { + (CodeActionKind::Deno(a), CodeActionKind::Deno(b)) => { + a.title.cmp(&b.title) + } + (CodeActionKind::DenoLint(a), CodeActionKind::DenoLint(b)) => { + a.title.cmp(&b.title) + } + (CodeActionKind::Tsc(a, _), CodeActionKind::Tsc(b, _)) => { + a.title.cmp(&b.title) + } + (CodeActionKind::Tsc(_, _), _) => Ordering::Less, + (CodeActionKind::Deno(_), CodeActionKind::Tsc(_, _)) => Ordering::Greater, + (CodeActionKind::Deno(_), CodeActionKind::DenoLint(_)) => Ordering::Less, + (CodeActionKind::DenoLint(_), _) => Ordering::Greater, + }); + + actions .into_iter() .map(|i| match i { CodeActionKind::Tsc(c, _) => lsp::CodeActionOrCommand::CodeAction(c), diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index f95e94182..9390f8f86 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -4045,6 +4045,24 @@ fn lsp_code_actions() { assert_eq!( res, json!([{ + "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" + } + }, { "title": "Add async modifier to containing function", "kind": "quickfix", "diagnostics": [{ @@ -4079,24 +4097,6 @@ fn lsp_code_actions() { }] }] } - }, { - "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" - } }]) ); let res = client @@ -4189,6 +4189,98 @@ fn lsp_code_actions() { } #[test] +fn test_lsp_code_actions_ordering() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + let diagnostics = client.did_open(json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": r#" + import "https://deno.land/x/a/mod.ts"; + let a = "a"; + console.log(a); + export function b(): void { + await Promise.resolve("b"); + } + "# + } + })); + let res = client.write_request( + "textDocument/codeAction", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { "line": 1, "character": 11 }, + "end": { "line": 6, "character": 12 } + }, + "context": { + "diagnostics": diagnostics.all(), + "only": ["quickfix"] + } + }), + ); + + // Simplify the serialization to `{ title, source }` for this test. + let mut actions: Vec<Value> = serde_json::from_value(res).unwrap(); + for action in &mut actions { + let action = action.as_object_mut().unwrap(); + let title = action.get("title").unwrap().as_str().unwrap().to_string(); + let diagnostics = action.get("diagnostics").unwrap().as_array().unwrap(); + let diagnostic = diagnostics.get(0).unwrap().as_object().unwrap(); + let source = diagnostic.get("source").unwrap(); + let source = source.as_str().unwrap().to_string(); + action.clear(); + action.insert("title".to_string(), serde_json::to_value(title).unwrap()); + action.insert("source".to_string(), serde_json::to_value(source).unwrap()); + } + let res = serde_json::to_value(actions).unwrap(); + + // Ensure ordering is "deno-ts" -> "deno" -> "deno-lint". + assert_eq!( + res, + json!([ + { + "title": "Add async modifier to containing function", + "source": "deno-ts", + }, + { + "title": "Cache \"https://deno.land/x/a/mod.ts\" and its dependencies.", + "source": "deno", + }, + { + "title": "Disable no-await-in-sync-fn for the entire file", + "source": "deno-lint", + }, + { + "title": "Disable no-await-in-sync-fn for this line", + "source": "deno-lint", + }, + { + "title": "Disable prefer-const for the entire file", + "source": "deno-lint", + }, + { + "title": "Disable prefer-const for this line", + "source": "deno-lint", + }, + { + "title": "Ignore lint errors for the entire file", + "source": "deno-lint", + }, + { + "title": "Ignore lint errors for the entire file", + "source": "deno-lint", + }, + ]) + ); +} + +#[test] fn lsp_code_actions_deno_cache() { let context = TestContextBuilder::new().use_temp_cwd().build(); let mut client = context.new_lsp_command().build(); @@ -4451,7 +4543,7 @@ export class DuckConfig { assert_eq!( res, json!([{ - "title": "Add import from \"./file02.ts\"", + "title": "Add all missing imports", "kind": "quickfix", "diagnostics": [{ "range": { @@ -4463,20 +4555,9 @@ export class DuckConfig { "source": "deno-ts", "message": "Cannot find name 'DuckConfigOptions'." }], - "edit": { - "documentChanges": [{ - "textDocument": { - "uri": "file:///a/file00.ts", - "version": 1 - }, - "edits": [{ - "range": { - "start": { "line": 0, "character": 0 }, - "end": { "line": 0, "character": 0 } - }, - "newText": "import { DuckConfigOptions } from \"./file02.ts\";\n\n" - }] - }] + "data": { + "specifier": "file:///a/file00.ts", + "fixId": "fixMissingImport" } }, { "title": "Add import from \"./file01.ts\"", @@ -4507,7 +4588,7 @@ export class DuckConfig { }] } }, { - "title": "Add all missing imports", + "title": "Add import from \"./file02.ts\"", "kind": "quickfix", "diagnostics": [{ "range": { @@ -4519,9 +4600,20 @@ export class DuckConfig { "source": "deno-ts", "message": "Cannot find name 'DuckConfigOptions'." }], - "data": { - "specifier": "file:///a/file00.ts", - "fixId": "fixMissingImport" + "edit": { + "documentChanges": [{ + "textDocument": { + "uri": "file:///a/file00.ts", + "version": 1 + }, + "edits": [{ + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 0 } + }, + "newText": "import { DuckConfigOptions } from \"./file02.ts\";\n\n" + }] + }] } }]) ); @@ -8093,7 +8185,7 @@ fn lsp_code_actions_ignore_lint() { assert_eq!( res, json!([{ - "title": "Disable prefer-const for this line", + "title": "Disable prefer-const for the entire file", "kind": "quickfix", "diagnostics": [{ "range": { @@ -8110,15 +8202,15 @@ fn lsp_code_actions_ignore_lint() { "changes": { "file:///a/file.ts": [{ "range": { - "start": { "line": 1, "character": 0 }, - "end": { "line": 1, "character": 0 } + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 0 } }, - "newText": "// deno-lint-ignore prefer-const\n" + "newText": "// deno-lint-ignore-file prefer-const\n" }] } } }, { - "title": "Disable prefer-const for the entire file", + "title": "Disable prefer-const for this line", "kind": "quickfix", "diagnostics": [{ "range": { @@ -8135,10 +8227,10 @@ fn lsp_code_actions_ignore_lint() { "changes": { "file:///a/file.ts": [{ "range": { - "start": { "line": 0, "character": 0 }, - "end": { "line": 0, "character": 0 } + "start": { "line": 1, "character": 0 }, + "end": { "line": 1, "character": 0 } }, - "newText": "// deno-lint-ignore-file prefer-const\n" + "newText": "// deno-lint-ignore prefer-const\n" }] } } @@ -8220,7 +8312,7 @@ console.log(snake_case); assert_eq!( res, json!([{ - "title": "Disable prefer-const for this line", + "title": "Disable prefer-const for the entire file", "kind": "quickfix", "diagnostics": [{ "range": { @@ -8237,15 +8329,15 @@ console.log(snake_case); "changes": { "file:///a/file.ts": [{ "range": { - "start": { "line": 3, "character": 0 }, - "end": { "line": 3, "character": 0 } + "start": { "line": 1, "character": 34 }, + "end": { "line": 1, "character": 34 } }, - "newText": "// deno-lint-ignore prefer-const\n" + "newText": " prefer-const" }] } } }, { - "title": "Disable prefer-const for the entire file", + "title": "Disable prefer-const for this line", "kind": "quickfix", "diagnostics": [{ "range": { @@ -8262,10 +8354,10 @@ console.log(snake_case); "changes": { "file:///a/file.ts": [{ "range": { - "start": { "line": 1, "character": 34 }, - "end": { "line": 1, "character": 34 } + "start": { "line": 3, "character": 0 }, + "end": { "line": 3, "character": 0 } }, - "newText": " prefer-const" + "newText": "// deno-lint-ignore prefer-const\n" }] } } |