Convert single-argument %-style format calls (#3600)

This commit is contained in:
Charlie Marsh 2023-03-20 23:35:10 -04:00 committed by GitHub
parent 318c2c80e2
commit e9f359ac5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 219 additions and 3 deletions

View File

@ -73,3 +73,13 @@ print("%s \N{snowman}" % (a,))
print("%(foo)s \N{snowman}" % {"foo": 1}) print("%(foo)s \N{snowman}" % {"foo": 1})
print(("foo %s " "bar %s") % (x, y)) print(("foo %s " "bar %s") % (x, y))
# Single-value expressions
print('Hello %s' % "World")
print('Hello %s' % f"World")
print('Hello %s (%s)' % bar)
print('Hello %s (%s)' % bar.baz)
print('Hello %s (%s)' % bar['bop'])
print('Hello %(arg)s' % bar)
print('Hello %(arg)s' % bar.baz)
print('Hello %(arg)s' % bar['bop'])

View File

@ -1,6 +1,4 @@
# OK # OK
"%s" % unknown_type
b"%s" % (b"bytestring",) b"%s" % (b"bytestring",)
"%*s" % (5, "hi") "%*s" % (5, "hi")
@ -57,3 +55,9 @@ pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
""" """
% (x,) % (x,)
) )
'Hello %s' % bar
'Hello %s' % bar.baz
'Hello %s' % bar['bop']

View File

@ -335,7 +335,9 @@ pub(crate) fn printf_string_formatting(
} }
// Parse each string segment. // Parse each string segment.
let mut format_strings = vec![]; let mut num_positional_arguments = 0;
let mut num_keyword_arguments = 0;
let mut format_strings = Vec::with_capacity(strings.len());
for (start, end) in &strings { for (start, end) in &strings {
let string = checker.locator.slice(Range::new(*start, *end)); let string = checker.locator.slice(Range::new(*start, *end));
let (Some(leader), Some(trailer)) = (leading_quote(string), trailing_quote(string)) else { let (Some(leader), Some(trailer)) = (leading_quote(string), trailing_quote(string)) else {
@ -351,12 +353,52 @@ pub(crate) fn printf_string_formatting(
return; return;
} }
// Count the number of positional and keyword arguments.
for (.., format_part) in format_string.iter() {
let CFormatPart::Spec(ref fmt) = format_part else {
continue;
};
if fmt.mapping_key.is_none() {
num_positional_arguments += 1;
} else {
num_keyword_arguments += 1;
}
}
// Convert the `%`-format string to a `.format` string.
let format_string = percent_to_format(&format_string); let format_string = percent_to_format(&format_string);
format_strings.push(format!("{leader}{format_string}{trailer}")); format_strings.push(format!("{leader}{format_string}{trailer}"));
} }
// Parse the parameters. // Parse the parameters.
let params_string = match right.node { let params_string = match right.node {
ExprKind::Constant { .. } | ExprKind::JoinedStr { .. } => {
format!("({})", checker.locator.slice(right))
}
ExprKind::Name { .. }
| ExprKind::Attribute { .. }
| ExprKind::Subscript { .. }
| ExprKind::Call { .. } => {
if num_keyword_arguments > 0 {
// If we have _any_ named fields, assume the right-hand side is a mapping.
format!("(**{})", checker.locator.slice(right))
} else if num_positional_arguments > 1 {
// If we have multiple fields, but no named fields, assume the right-hand side is a
// tuple.
format!("(*{})", checker.locator.slice(right))
} else {
// Otherwise, if we have a single field, we can't make any assumptions about the
// right-hand side. It _could_ be a tuple, but it could also be a single value,
// and we can't differentiate between them.
// For example:
// ```python
// x = (1,)
// print("%s" % x)
// print("{}".format(x))
// ```
return;
}
}
ExprKind::Tuple { .. } => clean_params_tuple(checker, right), ExprKind::Tuple { .. } => clean_params_tuple(checker, right),
ExprKind::Dict { .. } => { ExprKind::Dict { .. } => {
if let Some(params_string) = clean_params_dictionary(checker, right) { if let Some(params_string) = clean_params_dictionary(checker, right) {

View File

@ -582,4 +582,164 @@ expression: diagnostics
row: 75 row: 75
column: 35 column: 35
parent: ~ parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 78
column: 6
end_location:
row: 78
column: 26
fix:
content: "'Hello {}'.format(\"World\")"
location:
row: 78
column: 6
end_location:
row: 78
column: 26
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 79
column: 6
end_location:
row: 79
column: 27
fix:
content: "'Hello {}'.format(f\"World\")"
location:
row: 79
column: 6
end_location:
row: 79
column: 27
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 80
column: 6
end_location:
row: 80
column: 27
fix:
content: "'Hello {} ({})'.format(*bar)"
location:
row: 80
column: 6
end_location:
row: 80
column: 27
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 81
column: 6
end_location:
row: 81
column: 31
fix:
content: "'Hello {} ({})'.format(*bar.baz)"
location:
row: 81
column: 6
end_location:
row: 81
column: 31
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 82
column: 6
end_location:
row: 82
column: 34
fix:
content: "'Hello {} ({})'.format(*bar['bop'])"
location:
row: 82
column: 6
end_location:
row: 82
column: 34
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 83
column: 6
end_location:
row: 83
column: 27
fix:
content: "'Hello {arg}'.format(**bar)"
location:
row: 83
column: 6
end_location:
row: 83
column: 27
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 84
column: 6
end_location:
row: 84
column: 31
fix:
content: "'Hello {arg}'.format(**bar.baz)"
location:
row: 84
column: 6
end_location:
row: 84
column: 31
parent: ~
- kind:
name: PrintfStringFormatting
body: Use format specifiers instead of percent format
suggestion: Replace with format specifiers
fixable: true
location:
row: 85
column: 6
end_location:
row: 85
column: 34
fix:
content: "'Hello {arg}'.format(**bar['bop'])"
location:
row: 85
column: 6
end_location:
row: 85
column: 34
parent: ~