Skip to content

Commit

Permalink
[pyupgrade] Automatically rewrite format-strings to f-strings (#1905)
Browse files Browse the repository at this point in the history
  • Loading branch information
colin99d authored Jan 17, 2023
1 parent a486285 commit 1730f2a
Show file tree
Hide file tree
Showing 13 changed files with 774 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP028 | RewriteYieldFrom | Replace `yield` over `for` loop with `yield from` | 🛠 |
| UP029 | UnnecessaryBuiltinImport | Unnecessary builtin import: `...` | 🛠 |
| UP030 | FormatLiterals | Use implicit references for positional format fields | 🛠 |
| UP032 | FString | Use f-string instead of `format` call | 🛠 |

### pep8-naming (N)

Expand Down
86 changes: 86 additions & 0 deletions resources/test/fixtures/pyupgrade/UP032.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
###
# Errors
###

"{} {}".format(a, b)

"{1} {0}".format(a, b)

"{x.y}".format(x=z)

"{.x} {.y}".format(a, b)

"{} {}".format(a.b, c.d)

"{}".format(a())

"{}".format(a.b())

"{}".format(a.b().c())

"hello {}!".format(name)

"{}{b}{}".format(a, c, b=b)

"{}".format(0x0)

"{} {}".format(a, b)

"""{} {}""".format(a, b)

"foo{}".format(1)

r"foo{}".format(1)

x = "{a}".format(a=1)

print("foo {} ".format(x))

"{a[b]}".format(a=a)

"{a.a[b]}".format(a=a)

"{}{{}}{}".format(escaped, y)

"{}".format(a)

###
# Non-errors
###

# False-negative: RustPython doesn't parse the `\N{snowman}`.
"\N{snowman} {}".format(a)

"{".format(a)

"}".format(a)

"{} {}".format(*a)

"{0} {0}".format(arg)

"{x} {x}".format(arg)

"{x.y} {x.z}".format(arg)

b"{} {}".format(a, b)

"{:{}}".format(x, y)

"{}{}".format(a)

"" "{}".format(a["\\"])

"{}".format(a["b"])

r'"\N{snowman} {}".format(a)'

"{a}" "{b}".format(a=1, b=1)


async def c():
return "{}".format(await 3)


async def c():
return "{}".format(1 + await 3)
1 change: 1 addition & 0 deletions ruff.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,7 @@
"UP029",
"UP03",
"UP030",
"UP032",
"W",
"W2",
"W29",
Expand Down
9 changes: 7 additions & 2 deletions src/checkers/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1867,6 +1867,7 @@ where
|| self.settings.enabled.contains(&RuleCode::F525)
// pyupgrade
|| self.settings.enabled.contains(&RuleCode::UP030)
|| self.settings.enabled.contains(&RuleCode::UP032)
{
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Constant {
Expand All @@ -1890,8 +1891,8 @@ where
}
Ok(summary) => {
if self.settings.enabled.contains(&RuleCode::F522) {
pyflakes::rules::string_dot_format_extra_named_arguments(self,
&summary, keywords, location,
pyflakes::rules::string_dot_format_extra_named_arguments(
self, &summary, keywords, location,
);
}

Expand All @@ -1917,6 +1918,10 @@ where
if self.settings.enabled.contains(&RuleCode::UP030) {
pyupgrade::rules::format_literals(self, &summary, expr);
}

if self.settings.enabled.contains(&RuleCode::UP032) {
pyupgrade::rules::f_strings(self, &summary, expr);
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/docstrings/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
];

pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];

pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];
1 change: 1 addition & 0 deletions src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ ruff_macros::define_rule_mapping!(
UP028 => violations::RewriteYieldFrom,
UP029 => violations::UnnecessaryBuiltinImport,
UP030 => violations::FormatLiterals,
UP032 => violations::FString,
// pydocstyle
D100 => violations::PublicModule,
D101 => violations::PublicClass,
Expand Down
8 changes: 8 additions & 0 deletions src/rules/pydocstyle/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ pub fn leading_quote(content: &str) -> Option<&str> {
None
}

/// Return the trailing quote string for a docstring (e.g., `"""`).
pub fn trailing_quote(content: &str) -> Option<&&str> {
constants::TRIPLE_QUOTE_SUFFIXES
.iter()
.chain(constants::SINGLE_QUOTE_SUFFIXES)
.find(|&pattern| content.ends_with(pattern))
}

/// Return the index of the first logical line in a string.
pub fn logical_line(content: &str) -> Option<usize> {
// Find the first logical line.
Expand Down
1 change: 1 addition & 0 deletions src/rules/pyupgrade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ mod tests {
#[test_case(RuleCode::UP029, Path::new("UP029.py"); "UP029")]
#[test_case(RuleCode::UP030, Path::new("UP030_0.py"); "UP030_0")]
#[test_case(RuleCode::UP030, Path::new("UP030_1.py"); "UP030_1")]
#[test_case(RuleCode::UP032, Path::new("UP032.py"); "UP032")]
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
Loading

0 comments on commit 1730f2a

Please sign in to comment.