[`pyupgrade`] Show violations without auto-fix for `UP031` (#11229)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Sid 2024-08-14 13:59:40 +02:00 committed by GitHub
parent c487149b7d
commit 3898d737d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1387 additions and 27 deletions

View File

@ -139,3 +139,33 @@ print("%.20X" % 1)
print("%2X" % 1) print("%2X" % 1)
print("%02X" % 1) print("%02X" % 1)
# UP031 (no longer false negatives, but offer no fix because of more complex syntax)
"%d.%d" % (a, b)
"%*s" % (5, "hi")
"%d" % (flt,)
"%c" % (some_string,)
"%.2r" % (1.25)
"%.*s" % (5, "hi")
"%i" % (flt,)
"%()s" % {"": "empty"}
"%s" % {"k": "v"}
"%()s" % {"": "bar"}
"%(1)s" % {"1": "bar"}
"%(a)s" % {"a": 1, "a": 2}
"%(1)s" % {1: 2, "1": 2}
"%(and)s" % {"and": 2}

View File

@ -1,34 +1,8 @@
# OK # OK
b"%s" % (b"bytestring",) b"%s" % (b"bytestring",)
"%*s" % (5, "hi")
"%d" % (flt,)
"%c" % (some_string,)
"%4%" % () "%4%" % ()
"%.2r" % (1.25)
i % 3 i % 3
"%.*s" % (5, "hi")
"%i" % (flt,)
"%()s" % {"": "empty"}
"%s" % {"k": "v"}
"%(1)s" % {"1": "bar"}
"%(a)s" % {"a": 1, "a": 2}
pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"), pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
"%()s" % {"": "bar"}
"%(1)s" % {1: 2, "1": 2}
"%(and)s" % {"and": 2}

View File

@ -14,7 +14,7 @@ mod tests {
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::pyupgrade; use crate::rules::pyupgrade;
use crate::settings::types::PythonVersion; use crate::settings::types::{PreviewMode, PythonVersion};
use crate::test::test_path; use crate::test::test_path;
use crate::{assert_messages, settings}; use crate::{assert_messages, settings};
@ -100,6 +100,19 @@ mod tests {
Ok(()) Ok(())
} }
#[test_case(Rule::PrintfStringFormatting, Path::new("UP031_0.py"))]
fn preview(rule_code: Rule, path: &Path) -> Result<()> {
let diagnostics = test_path(
Path::new("pyupgrade").join(path),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test] #[test]
fn async_timeout_error_alias_not_applied_py310() -> Result<()> { fn async_timeout_error_alias_not_applied_py310() -> Result<()> {
let diagnostics = test_path( let diagnostics = test_path(

View File

@ -28,21 +28,29 @@ use crate::rules::pyupgrade::helpers::curly_escape;
/// formatting. /// formatting.
/// ///
/// ## Example /// ## Example
///
/// ```python /// ```python
/// "%s, %s" % ("Hello", "World") # "Hello, World" /// "%s, %s" % ("Hello", "World") # "Hello, World"
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
///
/// ```python /// ```python
/// "{}, {}".format("Hello", "World") # "Hello, World" /// "{}, {}".format("Hello", "World") # "Hello, World"
/// ``` /// ```
/// ///
/// ```python
/// f"{'Hello'}, {'World'}" # "Hello, World"
/// ```
///
/// ## Fix safety /// ## Fix safety
///
/// In cases where the format string contains a single generic format specifier /// In cases where the format string contains a single generic format specifier
/// (e.g. `%s`), and the right-hand side is an ambiguous expression, /// (e.g. `%s`), and the right-hand side is an ambiguous expression,
/// we cannot offer a safe fix. /// we cannot offer a safe fix.
/// ///
/// For example, given: /// For example, given:
///
/// ```python /// ```python
/// "%s" % val /// "%s" % val
/// ``` /// ```
@ -379,6 +387,11 @@ pub(crate) fn printf_string_formatting(
return; return;
}; };
if !convertible(&format_string, right) { if !convertible(&format_string, right) {
if checker.settings.preview.is_enabled() {
checker
.diagnostics
.push(Diagnostic::new(PrintfStringFormatting, string_expr.range()));
}
return; return;
} }
@ -437,6 +450,11 @@ pub(crate) fn printf_string_formatting(
let Some(params_string) = let Some(params_string) =
clean_params_dictionary(right, checker.locator(), checker.stylist()) clean_params_dictionary(right, checker.locator(), checker.stylist())
else { else {
if checker.settings.preview.is_enabled() {
checker
.diagnostics
.push(Diagnostic::new(PrintfStringFormatting, string_expr.range()));
}
return; return;
}; };
Cow::Owned(params_string) Cow::Owned(params_string)

View File

@ -1142,12 +1142,16 @@ UP031_0.py:140:7: UP031 [*] Use format specifiers instead of percent format
140 |-print("%2X" % 1) 140 |-print("%2X" % 1)
140 |+print("{:2X}".format(1)) 140 |+print("{:2X}".format(1))
141 141 | print("%02X" % 1) 141 141 | print("%02X" % 1)
142 142 |
143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax)
UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format
| |
140 | print("%2X" % 1) 140 | print("%2X" % 1)
141 | print("%02X" % 1) 141 | print("%02X" % 1)
| ^^^^^^^^^^ UP031 | ^^^^^^^^^^ UP031
142 |
143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax)
| |
= help: Replace with format specifiers = help: Replace with format specifiers
@ -1157,3 +1161,6 @@ UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format
140 140 | print("%2X" % 1) 140 140 | print("%2X" % 1)
141 |-print("%02X" % 1) 141 |-print("%02X" % 1)
141 |+print("{:02X}".format(1)) 141 |+print("{:02X}".format(1))
142 142 |
143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax)
144 144 |