Skip to content

Commit e4dc406

Browse files
authored
[refurb] Detect empty f-strings (FURB105) (#21348)
## Summary Fixes FURB105 (`print-empty-string`) to detect empty f-strings in addition to regular empty strings. Previously, the rule only flagged `print("")` but missed `print(f"")`. This fix ensures both cases are detected and can be automatically fixed. Fixes #21346 ## Problem Analysis The FURB105 rule checks for unnecessary empty strings passed to `print()` calls. The `is_empty_string` helper function was only checking for `Expr::StringLiteral` with empty values, but did not handle `Expr::FString` (f-strings). As a result, `print(f"")` was not being flagged as a violation, even though it's semantically equivalent to `print("")` and should be simplified to `print()`. The issue occurred because the function used a `matches!` macro that only checked for string literals: ```rust fn is_empty_string(expr: &Expr) -> bool { matches!( expr, Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) if value.is_empty() ) } ``` ## Approach 1. **Import the helper function**: Added `is_empty_f_string` to the imports from `ruff_python_ast::helpers`, which already provides logic to detect empty f-strings. 2. **Update `is_empty_string` function**: Changed the implementation from a `matches!` macro to a `match` expression that handles both string literals and f-strings: ```rust fn is_empty_string(expr: &Expr) -> bool { match expr { Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(), Expr::FString(f_string) => is_empty_f_string(f_string), _ => false, } } ``` The fix leverages the existing `is_empty_f_string` helper function which properly handles the complexity of f-strings, including nested f-strings and interpolated expressions. This ensures the detection is accurate and consistent with how empty strings are detected elsewhere in the codebase.
1 parent 1d18847 commit e4dc406

File tree

3 files changed

+78
-16
lines changed

3 files changed

+78
-16
lines changed

crates/ruff_linter/resources/test/fixtures/refurb/FURB105.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
print("", **kwargs)
2020
print(sep="\t")
2121
print(sep=print(1))
22+
print(f"")
23+
print(f"", sep=",")
24+
print(f"", end="bar")
2225

2326
# OK.
2427

@@ -33,3 +36,4 @@
3336
print("foo", "", "bar", "", sep=",")
3437
print("", "", **kwargs)
3538
print(*args, sep=",")
39+
print(f"foo")

crates/ruff_linter/src/rules/refurb/rules/print_empty_string.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ruff_macros::{ViolationMetadata, derive_message_formats};
2-
use ruff_python_ast::helpers::contains_effect;
2+
use ruff_python_ast::helpers::{contains_effect, is_empty_f_string};
33
use ruff_python_ast::{self as ast, Expr};
44
use ruff_python_codegen::Generator;
55
use ruff_python_semantic::SemanticModel;
@@ -194,13 +194,11 @@ pub(crate) fn print_empty_string(checker: &Checker, call: &ast::ExprCall) {
194194

195195
/// Check if an expression is a constant empty string.
196196
fn is_empty_string(expr: &Expr) -> bool {
197-
matches!(
198-
expr,
199-
Expr::StringLiteral(ast::ExprStringLiteral {
200-
value,
201-
..
202-
}) if value.is_empty()
203-
)
197+
match expr {
198+
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
199+
Expr::FString(f_string) => is_empty_f_string(f_string),
200+
_ => false,
201+
}
204202
}
205203

206204
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB105_FURB105.py.snap

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ help: Remove empty string
317317
19 + print(**kwargs)
318318
20 | print(sep="\t")
319319
21 | print(sep=print(1))
320-
22 |
320+
22 | print(f"")
321321

322322
FURB105 [*] Unnecessary separator passed to `print`
323323
--> FURB105.py:20:1
@@ -327,6 +327,7 @@ FURB105 [*] Unnecessary separator passed to `print`
327327
20 | print(sep="\t")
328328
| ^^^^^^^^^^^^^^^
329329
21 | print(sep=print(1))
330+
22 | print(f"")
330331
|
331332
help: Remove separator
332333
17 | print("", *args)
@@ -335,8 +336,8 @@ help: Remove separator
335336
- print(sep="\t")
336337
20 + print()
337338
21 | print(sep=print(1))
338-
22 |
339-
23 | # OK.
339+
22 | print(f"")
340+
23 | print(f"", sep=",")
340341

341342
FURB105 [*] Unnecessary separator passed to `print`
342343
--> FURB105.py:21:1
@@ -345,16 +346,75 @@ FURB105 [*] Unnecessary separator passed to `print`
345346
20 | print(sep="\t")
346347
21 | print(sep=print(1))
347348
| ^^^^^^^^^^^^^^^^^^^
348-
22 |
349-
23 | # OK.
349+
22 | print(f"")
350+
23 | print(f"", sep=",")
350351
|
351352
help: Remove separator
352353
18 | print("", *args, sep="")
353354
19 | print("", **kwargs)
354355
20 | print(sep="\t")
355356
- print(sep=print(1))
356357
21 + print()
357-
22 |
358-
23 | # OK.
359-
24 |
358+
22 | print(f"")
359+
23 | print(f"", sep=",")
360+
24 | print(f"", end="bar")
360361
note: This is an unsafe fix and may change runtime behavior
362+
363+
FURB105 [*] Unnecessary empty string passed to `print`
364+
--> FURB105.py:22:1
365+
|
366+
20 | print(sep="\t")
367+
21 | print(sep=print(1))
368+
22 | print(f"")
369+
| ^^^^^^^^^^
370+
23 | print(f"", sep=",")
371+
24 | print(f"", end="bar")
372+
|
373+
help: Remove empty string
374+
19 | print("", **kwargs)
375+
20 | print(sep="\t")
376+
21 | print(sep=print(1))
377+
- print(f"")
378+
22 + print()
379+
23 | print(f"", sep=",")
380+
24 | print(f"", end="bar")
381+
25 |
382+
383+
FURB105 [*] Unnecessary empty string and separator passed to `print`
384+
--> FURB105.py:23:1
385+
|
386+
21 | print(sep=print(1))
387+
22 | print(f"")
388+
23 | print(f"", sep=",")
389+
| ^^^^^^^^^^^^^^^^^^^
390+
24 | print(f"", end="bar")
391+
|
392+
help: Remove empty string and separator
393+
20 | print(sep="\t")
394+
21 | print(sep=print(1))
395+
22 | print(f"")
396+
- print(f"", sep=",")
397+
23 + print()
398+
24 | print(f"", end="bar")
399+
25 |
400+
26 | # OK.
401+
402+
FURB105 [*] Unnecessary empty string passed to `print`
403+
--> FURB105.py:24:1
404+
|
405+
22 | print(f"")
406+
23 | print(f"", sep=",")
407+
24 | print(f"", end="bar")
408+
| ^^^^^^^^^^^^^^^^^^^^^
409+
25 |
410+
26 | # OK.
411+
|
412+
help: Remove empty string
413+
21 | print(sep=print(1))
414+
22 | print(f"")
415+
23 | print(f"", sep=",")
416+
- print(f"", end="bar")
417+
24 + print(end="bar")
418+
25 |
419+
26 | # OK.
420+
27 |

0 commit comments

Comments
 (0)