[flake8-bugbear] Make fix unsafe if it deletes comments (B009) (#22656)

<!--
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:02:02 +03:00
committed by GitHub
parent a4ded5941c
commit 21dd6029f5
4 changed files with 39 additions and 1 deletions

View File

@@ -79,3 +79,10 @@ setattr(foo, "__debug__", 0)
# Example: the long s character "ſ" normalizes to "s" under NFKC.
getattr(foo, "ſ")
setattr(foo, "ſ", 1)
getattr(
obj,
# text
"foo",
)

View File

@@ -36,6 +36,9 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
/// (e.g., `obj.attr`), but does not normalize string arguments passed to `getattr`. Rewriting
/// `getattr(obj, "ſ")` to `obj.ſ` would be interpreted as `obj.s` at runtime, changing behavior.
///
/// Additionally, the fix is marked as unsafe if the expression contains comments,
/// as the replacement may remove comments attached to the original `getattr` call.
///
/// For example, the long s character `"ſ"` normalizes to `"s"` under NFKC, so:
/// ```python
/// # This accesses an attribute with the exact name "ſ" (if it exists)
@@ -89,7 +92,8 @@ pub(crate) fn getattr_with_constant(checker: &Checker, expr: &Expr, func: &Expr,
// attribute syntax (e.g., `obj.attr`) would normalize the name and potentially change
// program behavior.
let attr_name = value.to_str();
let is_unsafe = attr_name.nfkc().collect::<String>() != attr_name;
let has_comments = checker.comment_ranges().intersects(expr.range());
let is_unsafe = attr_name.nfkc().collect::<String>() != attr_name || has_comments;
let mut diagnostic = checker.report_diagnostic(GetAttrWithConstant, expr.range());
let edit = Edit::range_replacement(

View File

@@ -377,4 +377,28 @@ help: Replace `getattr` with attribute access
- getattr(foo, "ſ")
80 + foo.ſ
81 | setattr(foo, "ſ", 1)
82 |
83 |
note: This is an unsafe fix and may change runtime behavior
B009 [*] Do not call `getattr` with a constant attribute value. It is not any safer than normal property access.
--> B009_B010.py:84:1
|
84 | / getattr(
85 | | obj,
86 | | # text
87 | | "foo",
88 | | )
| |_^
|
help: Replace `getattr` with attribute access
81 | setattr(foo, "ſ", 1)
82 |
83 |
- getattr(
- obj,
- # text
- "foo",
- )
84 + obj.foo
note: This is an unsafe fix and may change runtime behavior

View File

@@ -133,4 +133,7 @@ help: Replace `setattr` with assignment
80 | getattr(foo, "ſ")
- setattr(foo, "ſ", 1)
81 + foo.ſ = 1
82 |
83 |
84 | getattr(
note: This is an unsafe fix and may change runtime behavior