[ruff] Make fix unsafe if it deletes comments (RUF019) (#22663)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
This commit is contained in:
chiri
2026-01-19 21:21:08 +03:00
committed by GitHub
parent a49d8af14c
commit d4a01501c7
3 changed files with 57 additions and 12 deletions

View File

@@ -22,3 +22,11 @@ v = "k" in d and d["k"]
if f() in d and d[f()]:
pass
if (
"key" in d
and # text
d ["key"]
):
...

View File

@@ -1,3 +1,4 @@
use ruff_diagnostics::Applicability;
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr};
@@ -28,6 +29,9 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
/// if dct.get("key"):
/// ...
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as safe, unless the expression contains comments.
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.2.0")]
pub(crate) struct UnnecessaryKeyCheck;
@@ -104,18 +108,28 @@ pub(crate) fn unnecessary_key_check(checker: &Checker, expr: &Expr) {
}
let mut diagnostic = checker.report_diagnostic(UnnecessaryKeyCheck, expr.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!(
"{}.get({})",
checker.locator().slice(
parenthesized_range(obj_right.into(), right.into(), checker.tokens(),)
.unwrap_or(obj_right.range())
),
checker.locator().slice(
parenthesized_range(key_right.into(), right.into(), checker.tokens(),)
.unwrap_or(key_right.range())
let applicability = if checker.comment_ranges().intersects(expr.range()) {
Applicability::Unsafe
} else {
Applicability::Safe
};
diagnostic.set_fix(Fix::applicable_edit(
Edit::range_replacement(
format!(
"{}.get({})",
checker.locator().slice(
parenthesized_range(obj_right.into(), right.into(), checker.tokens(),)
.unwrap_or(obj_right.range())
),
checker.locator().slice(
parenthesized_range(key_right.into(), right.into(), checker.tokens(),)
.unwrap_or(key_right.range())
),
),
expr.range(),
),
expr.range(),
)));
applicability,
));
}

View File

@@ -114,3 +114,26 @@ help: Replace with `dict.get`
19 |
20 | # OK
21 | v = "k" in d and d["k"]
RUF019 [*] Unnecessary key check before dictionary access
--> RUF019.py:28:9
|
27 | if (
28 | / "key" in d
29 | | and # text
30 | | d ["key"]
| |_________________^
31 | ):
32 | ...
|
help: Replace with `dict.get`
25 |
26 |
27 | if (
- "key" in d
- and # text
- d ["key"]
28 + d.get("key")
29 | ):
30 | ...
note: This is an unsafe fix and may change runtime behavior