diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF065_0.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF065_0.py index 879fb186bf543..ea890c9782286 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF065_0.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF065_0.py @@ -34,6 +34,18 @@ info("Hello %r", repr("World!")) log(logging.INFO, "Hello %r", repr("World!")) +# https://github.com/astral-sh/ruff/issues/26912 +# A `*args` unpacking can supply any number of values, so arguments following it +# don't necessarily line up with the specifier in the same position. +args = ("a", "b") +logging.warning("%s%s%s%s %r", *"1234", str(5)) +logging.warning("%s %s", *args, repr(5)) +log(logging.INFO, "%s %s", *args, ascii(5)) + +# Arguments *before* the unpacking are still correctly aligned, so flag these. +logging.warning("%s %s", str(5), *args) +logging.warning("%s %s", repr(5), *args) + def str(s): return f"str = {s}" # Don't flag this logging.info("Hello %s", str("World!")) diff --git a/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs index 920f6c73a5e64..512b513a311e1 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs @@ -137,7 +137,15 @@ pub(crate) fn logging_eager_conversion(checker: &Checker, call: &ast::ExprCall) None } }) - .zip(call.arguments.args.iter().skip(msg_pos + 1)) + // A `*args` unpacking can supply any number of values, so every argument + // after it may line up with a different specifier than its position suggests. + .zip( + call.arguments + .args + .iter() + .skip(msg_pos + 1) + .take_while(|arg| !arg.is_starred_expr()), + ) { // Check if the argument is a call to eagerly format a value if let Expr::Call(ast::ExprCall { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065_0.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065_0.py.snap index 9ac438216a1a7..de384f95cd37d 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065_0.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065_0.py.snap @@ -81,139 +81,159 @@ RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` inste 29 | # %r + str() | +RUF065 Unnecessary `str()` conversion when formatting with `%s` + --> RUF065_0.py:46:26 + | +45 | # Arguments *before* the unpacking are still correctly aligned, so flag these. +46 | logging.warning("%s %s", str(5), *args) + | ^^^^^^ +47 | logging.warning("%s %s", repr(5), *args) + | + +RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s` + --> RUF065_0.py:47:26 + | +45 | # Arguments *before* the unpacking are still correctly aligned, so flag these. +46 | logging.warning("%s %s", str(5), *args) +47 | logging.warning("%s %s", repr(5), *args) + | ^^^^^^^ +48 | +49 | def str(s): return f"str = {s}" + | + RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s` - --> RUF065_0.py:44:32 + --> RUF065_0.py:56:32 | -42 | logging.warning("Value: %r", repr(42)) -43 | logging.error("Error: %r", repr([1, 2, 3])) -44 | logging.info("Debug info: %s", repr("test\nstring")) +54 | logging.warning("Value: %r", repr(42)) +55 | logging.error("Error: %r", repr([1, 2, 3])) +56 | logging.info("Debug info: %s", repr("test\nstring")) | ^^^^^^^^^^^^^^^^^^^^ -45 | logging.warning("Value: %s", repr(42)) +57 | logging.warning("Value: %s", repr(42)) | RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s` - --> RUF065_0.py:45:30 + --> RUF065_0.py:57:30 | -43 | logging.error("Error: %r", repr([1, 2, 3])) -44 | logging.info("Debug info: %s", repr("test\nstring")) -45 | logging.warning("Value: %s", repr(42)) +55 | logging.error("Error: %r", repr([1, 2, 3])) +56 | logging.info("Debug info: %s", repr("test\nstring")) +57 | logging.warning("Value: %s", repr(42)) | ^^^^^^^^ -46 | -47 | # %s + ascii() +58 | +59 | # %s + ascii() | RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s` - --> RUF065_0.py:48:27 + --> RUF065_0.py:60:27 | -47 | # %s + ascii() -48 | logging.info("ASCII: %s", ascii("Hello\nWorld")) +59 | # %s + ascii() +60 | logging.info("ASCII: %s", ascii("Hello\nWorld")) | ^^^^^^^^^^^^^^^^^^^^^ -49 | logging.warning("ASCII: %s", ascii("test")) +61 | logging.warning("ASCII: %s", ascii("test")) | RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s` - --> RUF065_0.py:49:30 + --> RUF065_0.py:61:30 | -47 | # %s + ascii() -48 | logging.info("ASCII: %s", ascii("Hello\nWorld")) -49 | logging.warning("ASCII: %s", ascii("test")) +59 | # %s + ascii() +60 | logging.info("ASCII: %s", ascii("Hello\nWorld")) +61 | logging.warning("ASCII: %s", ascii("test")) | ^^^^^^^^^^^^^ -50 | -51 | # %s + oct() +62 | +63 | # %s + oct() | RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s` - --> RUF065_0.py:52:27 + --> RUF065_0.py:64:27 | -51 | # %s + oct() -52 | logging.info("Octal: %s", oct(42)) +63 | # %s + oct() +64 | logging.info("Octal: %s", oct(42)) | ^^^^^^^ -53 | logging.warning("Octal: %s", oct(255)) +65 | logging.warning("Octal: %s", oct(255)) | RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s` - --> RUF065_0.py:53:30 + --> RUF065_0.py:65:30 | -51 | # %s + oct() -52 | logging.info("Octal: %s", oct(42)) -53 | logging.warning("Octal: %s", oct(255)) +63 | # %s + oct() +64 | logging.info("Octal: %s", oct(42)) +65 | logging.warning("Octal: %s", oct(255)) | ^^^^^^^^ -54 | -55 | # %s + hex() +66 | +67 | # %s + hex() | RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s` - --> RUF065_0.py:56:25 + --> RUF065_0.py:68:25 | -55 | # %s + hex() -56 | logging.info("Hex: %s", hex(42)) +67 | # %s + hex() +68 | logging.info("Hex: %s", hex(42)) | ^^^^^^^ -57 | logging.warning("Hex: %s", hex(255)) +69 | logging.warning("Hex: %s", hex(255)) | RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s` - --> RUF065_0.py:57:28 + --> RUF065_0.py:69:28 | -55 | # %s + hex() -56 | logging.info("Hex: %s", hex(42)) -57 | logging.warning("Hex: %s", hex(255)) +67 | # %s + hex() +68 | logging.info("Hex: %s", hex(42)) +69 | logging.warning("Hex: %s", hex(255)) | ^^^^^^^^ | RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s` - --> RUF065_0.py:63:19 + --> RUF065_0.py:75:19 | -61 | from logging import info, log -62 | -63 | info("ASCII: %s", ascii("Hello\nWorld")) +73 | from logging import info, log +74 | +75 | info("ASCII: %s", ascii("Hello\nWorld")) | ^^^^^^^^^^^^^^^^^^^^^ -64 | log(logging.INFO, "ASCII: %s", ascii("test")) +76 | log(logging.INFO, "ASCII: %s", ascii("test")) | RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s` - --> RUF065_0.py:64:32 + --> RUF065_0.py:76:32 | -63 | info("ASCII: %s", ascii("Hello\nWorld")) -64 | log(logging.INFO, "ASCII: %s", ascii("test")) +75 | info("ASCII: %s", ascii("Hello\nWorld")) +76 | log(logging.INFO, "ASCII: %s", ascii("test")) | ^^^^^^^^^^^^^ -65 | -66 | info("Octal: %s", oct(42)) +77 | +78 | info("Octal: %s", oct(42)) | RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s` - --> RUF065_0.py:66:19 + --> RUF065_0.py:78:19 | -64 | log(logging.INFO, "ASCII: %s", ascii("test")) -65 | -66 | info("Octal: %s", oct(42)) +76 | log(logging.INFO, "ASCII: %s", ascii("test")) +77 | +78 | info("Octal: %s", oct(42)) | ^^^^^^^ -67 | log(logging.INFO, "Octal: %s", oct(255)) +79 | log(logging.INFO, "Octal: %s", oct(255)) | RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s` - --> RUF065_0.py:67:32 + --> RUF065_0.py:79:32 | -66 | info("Octal: %s", oct(42)) -67 | log(logging.INFO, "Octal: %s", oct(255)) +78 | info("Octal: %s", oct(42)) +79 | log(logging.INFO, "Octal: %s", oct(255)) | ^^^^^^^^ -68 | -69 | info("Hex: %s", hex(42)) +80 | +81 | info("Hex: %s", hex(42)) | RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s` - --> RUF065_0.py:69:17 + --> RUF065_0.py:81:17 | -67 | log(logging.INFO, "Octal: %s", oct(255)) -68 | -69 | info("Hex: %s", hex(42)) +79 | log(logging.INFO, "Octal: %s", oct(255)) +80 | +81 | info("Hex: %s", hex(42)) | ^^^^^^^ -70 | log(logging.INFO, "Hex: %s", hex(255)) +82 | log(logging.INFO, "Hex: %s", hex(255)) | RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s` - --> RUF065_0.py:70:30 + --> RUF065_0.py:82:30 | -69 | info("Hex: %s", hex(42)) -70 | log(logging.INFO, "Hex: %s", hex(255)) +81 | info("Hex: %s", hex(42)) +82 | log(logging.INFO, "Hex: %s", hex(255)) | ^^^^^^^^ |