[ty] Do not prefer unsafe fixes in the language server (#27822)

This commit is contained in:
Micha Reiser authored and GitHub committed 2026-08-18 12:49:33 +02:00
1 parent dbf935d74b
commit 938e41e7a9
4 files changed
+140 -1

No files matched your search

@@ -647,6 +647,7 @@ pub(crate) struct FullDiagnosticData {
pub(crate) struct DiagnosticFixData {
pub(crate) fix_title: String,
pub(crate) edits: HashMap<Uri, Vec<lsp_types::TextEdit>>,
pub(crate) preferred: bool,
}
#[derive(Serialize, Deserialize)]
@@ -722,6 +723,7 @@ impl DiagnosticData {
.map(ToString::to_string)
.unwrap_or_else(|| format!("Fix {}", diagnostic.id())),
edits: lsp_edits,
preferred: fix.applies(Applicability::Safe),
})
}
}
@@ -81,7 +81,7 @@ impl BackgroundDocumentRequestHandler for CodeActionRequestHandler {
document_changes: None,
change_annotations: None,
}),
is_preferred: Some(true),
is_preferred: Some(fix.preferred),
command: None,
disabled: None,
data: None,
@@ -82,6 +82,41 @@ unused-ignore-comment = \"warn\"
Ok(())
}
#[test]
fn code_action_unsafe_fix() -> Result<()> {
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
// Removing the suppression is unsafe because it would activate `fmt: off`.
let foo_content = "\
# ty: ignore[division-by-zero] # fmt: off
x = 20 / 2
";
let ty_toml = SystemPath::new("ty.toml");
let ty_toml_content = "\
[rules]
unused-ignore-comment = \"warn\"
";
let mut server = TestServerBuilder::new()?
.with_workspace(workspace_root, None)?
.with_file(ty_toml, ty_toml_content)?
.with_file(foo, foo_content)?
.build()
.wait_until_workspaces_are_initialized();
server.open_text_document(foo, foo_content, 1);
let diagnostics = server.document_diagnostic_request(foo, None);
let code_action_params = code_actions_at(&server, diagnostics, foo, full_range(foo_content));
let code_action_id = server.send_request::<CodeActionRequest>(code_action_params);
let code_actions = server.await_response::<CodeActionRequest>(&code_action_id);
insta::assert_json_snapshot!(code_actions);
Ok(())
}
#[test]
fn no_code_action_for_non_overlapping_range_on_same_line() -> Result<()> {
let workspace_root = SystemPath::new("src");
@@ -0,0 +1,102 @@
---
source: crates/ty_server/tests/e2e/code_actions.rs
expression: code_actions
---
[
{
"title": "Remove the unused suppression comment",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 31
}
},
"severity": 2,
"code": "unused-ignore-comment",
"codeDescription": {
"href": "https://ty.dev/rules#unused-ignore-comment"
},
"source": "ty",
"message": "Unused `ty: ignore` directive\n\nhelp: Remove the unused suppression comment",
"tags": [
1
]
}
],
"isPreferred": false,
"edit": {
"changes": {
"file://<temp_dir>/src/foo.py": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 31
}
},
"newText": ""
}
]
}
}
},
{
"title": "Ignore 'unused-ignore-comment' for this line",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 31
}
},
"severity": 2,
"code": "unused-ignore-comment",
"codeDescription": {
"href": "https://ty.dev/rules#unused-ignore-comment"
},
"source": "ty",
"message": "Unused `ty: ignore` directive\n\nhelp: Remove the unused suppression comment",
"tags": [
1
]
}
],
"isPreferred": false,
"edit": {
"changes": {
"file://<temp_dir>/src/foo.py": [
{
"range": {
"start": {
"line": 0,
"character": 41
},
"end": {
"line": 0,
"character": 41
}
},
"newText": " # ty: ignore[unused-ignore-comment]"
}
]
}
}
}
]