-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[refurb] Auto-fix annotated assignments (FURB101)
#21278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| FURB101 | 14 | 0 | 0 | 14 | 0 |
ntBre
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, the results look good here, including the ecosystem check. I just had a couple of suggestions for simplification.
| } | ||
|
|
||
| let target = match self.with_stmt.body.first() { | ||
| let (target, annotation) = match self.with_stmt.body.first() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have noticed this in the earlier PR, but this actually looks quite repetitive with the check inside of generate_fix. I think we can combine this check and this check:
matches!(with_stmt.body.as_slice(), [Stmt::Assign(_)]))generate_fix seems like the right place to do that to me, which should allow us to decrease the number of arguments since we're already passing the with_stmt itself.
| let annotation_code = self | ||
| .checker | ||
| .generator() | ||
| .expr(ann_assign.annotation.as_ref()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to use the generator here, or can we just slice the &str out of the input?
| let annotation_code = self | |
| .checker | |
| .generator() | |
| .expr(ann_assign.annotation.as_ref()); | |
| let annotation_code = self | |
| .checker | |
| .locator() | |
| .slice(ann_assign.annotation.range()); |
(untested but that's what I had in mind)
the first() and contains_range() checks weren't exactly correct, as shown in the new tests. using first() instead of an exact length check could lead to errors where additional assignment targets were dropped. using contains_range() instead of an exact range comparison could similarly drop other parts of the expression beyond the read() call.
|
I merged the conflicting test changes I made in another PR, simplified the implementation a bit further, and then fixed a couple of preexisting bugs I noticed. The ecosystem comment doesn't seem to be updating, but I downloaded the artifact and checked that instead. It's showing -188 fixes, and from the ones I've clicked on this seems correct. The two bugs I fixed were that the content, x = f.read(), 2and the content = process_contents(f.read())but we were then replacing the entire assignment statement and discarding the The fix is now fairly restricted. We may want to consider expanding it in the future by doing a more targeted replacement of the |
refurb] Fix annotated assignments blocking FURB101 fix (FURB101)refurb] Auto-fix annotated assignments (FURB101)
Summary
Fixed FURB101 (
read-whole-file) to handle annotated assignments. Previously, the rule would detect violations in code likecontents: str = f.read()but fail to generate a fix. Now it correctly generates fixes that preserve type annotations (e.g.,contents: str = Path("file.txt").read_text(encoding="utf-8")).Fixes #21274
Problem Analysis
The FURB101 rule was only checking for
Stmt::Assignstatements when determining whether a fix could be applied. When encountering annotated assignments (Stmt::AnnAssign) likecontents: str = f.read(), the rule would:visit_exprmethod only matchedStmt::Assign, notStmt::AnnAssigngenerate_fixfunction only acceptedStmt::Assignin its body validationThis occurred because Python's AST represents annotated assignments as a different node type (
StmtAnnAssign) with separate fields for the target, annotation, and value, unlike regular assignments which use a list of targets.Approach
The fix extends the rule to handle both assignment types:
Updated
visit_exprmethod: Now matches bothStmt::AssignandStmt::AnnAssign, extracting:Updated
generate_fixfunction:annotation: Option<String>parameter to accept annotation codeStmt::AssignandStmt::AnnAssign{var}: {annotation} = {binding}({filename_code}).{suggestion}Added test case: Added an annotated assignment test case to verify the fix works correctly.
The implementation maintains backward compatibility with regular assignments while adding support for annotated assignments, ensuring type annotations are preserved in the generated fixes.