Skip to content

Commit ca79338

Browse files
authored
[ruff] Trigger RUF037 for empty string and byte strings (#18862)
1 parent e474f36 commit ca79338

4 files changed

Lines changed: 145 additions & 1 deletion

File tree

crates/ruff_linter/resources/test/fixtures/ruff/RUF037.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from collections import deque
21
import collections
2+
from collections import deque
33

44

55
def f():
@@ -91,3 +91,14 @@ def f():
9191

9292
def f():
9393
deque([], iterable=[])
94+
95+
# https://github.com/astral-sh/ruff/issues/18854
96+
deque("")
97+
deque(b"")
98+
deque(f"")
99+
deque(f"" "")
100+
deque(f"" f"")
101+
deque("abc") # OK
102+
deque(b"abc") # OK
103+
deque(f"" "a") # OK
104+
deque(f"{x}" "") # OK

crates/ruff_linter/src/rules/ruff/rules/unnecessary_literal_within_deque_call.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ pub(crate) fn unnecessary_literal_within_deque_call(checker: &Checker, deque: &a
100100
})
101101
&& call.arguments.is_empty()
102102
}
103+
Expr::StringLiteral(string) => string.value.is_empty(),
104+
Expr::BytesLiteral(bytes) => bytes.value.is_empty(),
105+
Expr::FString(fstring) => fstring.value.is_empty_literal(),
103106
_ => false,
104107
};
105108
if !is_empty_literal {

crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF037_RUF037.py.snap

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ RUF037.py:93:5: RUF037 [*] Unnecessary empty iterable within a deque call
246246
92 | def f():
247247
93 | deque([], iterable=[])
248248
| ^^^^^^^^^^^^^^^^^^^^^^ RUF037
249+
94 |
250+
95 | # https://github.com/astral-sh/ruff/issues/18854
249251
|
250252
= help: Replace with `deque()`
251253

@@ -255,3 +257,110 @@ RUF037.py:93:5: RUF037 [*] Unnecessary empty iterable within a deque call
255257
92 92 | def f():
256258
93 |- deque([], iterable=[])
257259
93 |+ deque([])
260+
94 94 |
261+
95 95 | # https://github.com/astral-sh/ruff/issues/18854
262+
96 96 | deque("")
263+
264+
RUF037.py:96:1: RUF037 [*] Unnecessary empty iterable within a deque call
265+
|
266+
95 | # https://github.com/astral-sh/ruff/issues/18854
267+
96 | deque("")
268+
| ^^^^^^^^^ RUF037
269+
97 | deque(b"")
270+
98 | deque(f"")
271+
|
272+
= help: Replace with `deque()`
273+
274+
Safe fix
275+
93 93 | deque([], iterable=[])
276+
94 94 |
277+
95 95 | # https://github.com/astral-sh/ruff/issues/18854
278+
96 |-deque("")
279+
96 |+deque()
280+
97 97 | deque(b"")
281+
98 98 | deque(f"")
282+
99 99 | deque(f"" "")
283+
284+
RUF037.py:97:1: RUF037 [*] Unnecessary empty iterable within a deque call
285+
|
286+
95 | # https://github.com/astral-sh/ruff/issues/18854
287+
96 | deque("")
288+
97 | deque(b"")
289+
| ^^^^^^^^^^ RUF037
290+
98 | deque(f"")
291+
99 | deque(f"" "")
292+
|
293+
= help: Replace with `deque()`
294+
295+
Safe fix
296+
94 94 |
297+
95 95 | # https://github.com/astral-sh/ruff/issues/18854
298+
96 96 | deque("")
299+
97 |-deque(b"")
300+
97 |+deque()
301+
98 98 | deque(f"")
302+
99 99 | deque(f"" "")
303+
100 100 | deque(f"" f"")
304+
305+
RUF037.py:98:1: RUF037 [*] Unnecessary empty iterable within a deque call
306+
|
307+
96 | deque("")
308+
97 | deque(b"")
309+
98 | deque(f"")
310+
| ^^^^^^^^^^ RUF037
311+
99 | deque(f"" "")
312+
100 | deque(f"" f"")
313+
|
314+
= help: Replace with `deque()`
315+
316+
Safe fix
317+
95 95 | # https://github.com/astral-sh/ruff/issues/18854
318+
96 96 | deque("")
319+
97 97 | deque(b"")
320+
98 |-deque(f"")
321+
98 |+deque()
322+
99 99 | deque(f"" "")
323+
100 100 | deque(f"" f"")
324+
101 101 | deque("abc") # OK
325+
326+
RUF037.py:99:1: RUF037 [*] Unnecessary empty iterable within a deque call
327+
|
328+
97 | deque(b"")
329+
98 | deque(f"")
330+
99 | deque(f"" "")
331+
| ^^^^^^^^^^^^^ RUF037
332+
100 | deque(f"" f"")
333+
101 | deque("abc") # OK
334+
|
335+
= help: Replace with `deque()`
336+
337+
Safe fix
338+
96 96 | deque("")
339+
97 97 | deque(b"")
340+
98 98 | deque(f"")
341+
99 |-deque(f"" "")
342+
99 |+deque()
343+
100 100 | deque(f"" f"")
344+
101 101 | deque("abc") # OK
345+
102 102 | deque(b"abc") # OK
346+
347+
RUF037.py:100:1: RUF037 [*] Unnecessary empty iterable within a deque call
348+
|
349+
98 | deque(f"")
350+
99 | deque(f"" "")
351+
100 | deque(f"" f"")
352+
| ^^^^^^^^^^^^^^ RUF037
353+
101 | deque("abc") # OK
354+
102 | deque(b"abc") # OK
355+
|
356+
= help: Replace with `deque()`
357+
358+
Safe fix
359+
97 97 | deque(b"")
360+
98 98 | deque(f"")
361+
99 99 | deque(f"" "")
362+
100 |-deque(f"" f"")
363+
100 |+deque()
364+
101 101 | deque("abc") # OK
365+
102 102 | deque(b"abc") # OK
366+
103 103 | deque(f"" "a") # OK

crates/ruff_python_ast/src/nodes.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,20 @@ impl FStringValue {
504504
pub fn elements(&self) -> impl Iterator<Item = &InterpolatedStringElement> {
505505
self.f_strings().flat_map(|fstring| fstring.elements.iter())
506506
}
507+
508+
/// Returns `true` if the node represents an empty f-string literal.
509+
///
510+
/// Noteh that a [`FStringValue`] node will always have >= 1 [`FStringPart`]s inside it.
511+
/// This method checks whether the value of the concatenated parts is equal to the empty
512+
/// f-string, not whether the f-string has 0 parts inside it.
513+
pub fn is_empty_literal(&self) -> bool {
514+
match &self.inner {
515+
FStringValueInner::Single(fstring_part) => fstring_part.is_empty_literal(),
516+
FStringValueInner::Concatenated(fstring_parts) => {
517+
fstring_parts.iter().all(FStringPart::is_empty_literal)
518+
}
519+
}
520+
}
507521
}
508522

509523
impl<'a> IntoIterator for &'a FStringValue {
@@ -550,6 +564,13 @@ impl FStringPart {
550564
Self::FString(f_string) => f_string.flags.quote_style(),
551565
}
552566
}
567+
568+
pub fn is_empty_literal(&self) -> bool {
569+
match &self {
570+
FStringPart::Literal(string_literal) => string_literal.value.is_empty(),
571+
FStringPart::FString(f_string) => f_string.elements.is_empty(),
572+
}
573+
}
553574
}
554575

555576
impl Ranged for FStringPart {

0 commit comments

Comments
 (0)