Skip to content

Commit 5ab3570

Browse files
authored
Extend F507 to flag %-format strings with zero placeholders (#24215)
1 parent 690ef81 commit 5ab3570

3 files changed

Lines changed: 109 additions & 1 deletion

File tree

crates/ruff_linter/resources/test/fixtures/pyflakes/F50x.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,17 @@
5252
# ok: ternary/binop where one branch could be a tuple → Unknown
5353
'%s %s' % (a if cond else b)
5454
'%s %s' % (a + b)
55+
56+
# F507: zero placeholders with literal non-tuple RHS
57+
'hello' % 42 # F507
58+
'' % 42 # F507
59+
'hello' % (1,) # F507
60+
# F507: zero placeholders with variable RHS (intentional use is very unlikely)
61+
banana = 42
62+
'hello' % banana # F507
63+
'' % banana # F507
64+
'hello' % unknown_var # F507
65+
'hello' % get_value() # F507
66+
'hello' % obj.attr # F507
67+
# ok: zero placeholders with empty tuple
68+
'hello' % ()

crates/ruff_linter/src/rules/pyflakes/rules/strings.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,14 @@ pub(crate) fn percent_format_positional_count_mismatch(
772772
location,
773773
);
774774
}
775+
} else if summary.num_positional == 0 {
776+
// When the format string has no placeholders, only `()` or `{}` would
777+
// succeed at runtime. The chance that this is intentional is very low,
778+
// so flag any RHS that isn't an empty tuple or empty dict literal.
779+
checker.report_diagnostic(
780+
PercentFormatPositionalCountMismatch { wanted: 0, got: 1 },
781+
location,
782+
);
775783
}
776784
}
777785

crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F507_F50x.py.snap

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
22
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
3-
assertion_line: 192
43
---
54
F507 `%`-format string has 2 placeholder(s) but 1 substitution(s)
65
--> F50x.py:5:1
@@ -187,3 +186,90 @@ F507 `%`-format string has 2 placeholder(s) but 1 substitution(s)
187186
43 | # ok: single placeholder with literal RHS
188187
44 | '%s' % 42
189188
|
189+
190+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
191+
--> F50x.py:57:1
192+
|
193+
56 | # F507: zero placeholders with literal non-tuple RHS
194+
57 | 'hello' % 42 # F507
195+
| ^^^^^^^^^^^^
196+
58 | '' % 42 # F507
197+
59 | 'hello' % (1,) # F507
198+
|
199+
200+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
201+
--> F50x.py:58:1
202+
|
203+
56 | # F507: zero placeholders with literal non-tuple RHS
204+
57 | 'hello' % 42 # F507
205+
58 | '' % 42 # F507
206+
| ^^^^^^^
207+
59 | 'hello' % (1,) # F507
208+
60 | # F507: zero placeholders with variable RHS (intentional use is very unlikely)
209+
|
210+
211+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
212+
--> F50x.py:59:1
213+
|
214+
57 | 'hello' % 42 # F507
215+
58 | '' % 42 # F507
216+
59 | 'hello' % (1,) # F507
217+
| ^^^^^^^^^^^^^^
218+
60 | # F507: zero placeholders with variable RHS (intentional use is very unlikely)
219+
61 | banana = 42
220+
|
221+
222+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
223+
--> F50x.py:62:1
224+
|
225+
60 | # F507: zero placeholders with variable RHS (intentional use is very unlikely)
226+
61 | banana = 42
227+
62 | 'hello' % banana # F507
228+
| ^^^^^^^^^^^^^^^^
229+
63 | '' % banana # F507
230+
64 | 'hello' % unknown_var # F507
231+
|
232+
233+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
234+
--> F50x.py:63:1
235+
|
236+
61 | banana = 42
237+
62 | 'hello' % banana # F507
238+
63 | '' % banana # F507
239+
| ^^^^^^^^^^^
240+
64 | 'hello' % unknown_var # F507
241+
65 | 'hello' % get_value() # F507
242+
|
243+
244+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
245+
--> F50x.py:64:1
246+
|
247+
62 | 'hello' % banana # F507
248+
63 | '' % banana # F507
249+
64 | 'hello' % unknown_var # F507
250+
| ^^^^^^^^^^^^^^^^^^^^^
251+
65 | 'hello' % get_value() # F507
252+
66 | 'hello' % obj.attr # F507
253+
|
254+
255+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
256+
--> F50x.py:65:1
257+
|
258+
63 | '' % banana # F507
259+
64 | 'hello' % unknown_var # F507
260+
65 | 'hello' % get_value() # F507
261+
| ^^^^^^^^^^^^^^^^^^^^^
262+
66 | 'hello' % obj.attr # F507
263+
67 | # ok: zero placeholders with empty tuple
264+
|
265+
266+
F507 `%`-format string has 0 placeholder(s) but 1 substitution(s)
267+
--> F50x.py:66:1
268+
|
269+
64 | 'hello' % unknown_var # F507
270+
65 | 'hello' % get_value() # F507
271+
66 | 'hello' % obj.attr # F507
272+
| ^^^^^^^^^^^^^^^^^^
273+
67 | # ok: zero placeholders with empty tuple
274+
68 | 'hello' % ()
275+
|

0 commit comments

Comments
 (0)