From 090c1a4a19fec62622ebe75209ff8bf11db4b8ca Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 10 Oct 2023 12:31:09 -0400 Subject: [PATCH] Avoid converting f-strings within Django `gettext` calls (#7898) ## Summary Django's `gettext` doesn't support f-strings, so we should avoid translating `.format` calls in those cases. Closes https://github.com/astral-sh/ruff/issues/7891. --- .../test/fixtures/pyupgrade/UP032_3.py | 9 +++++++++ crates/ruff_linter/src/rules/pyupgrade/mod.rs | 1 + .../src/rules/pyupgrade/rules/f_strings.rs | 20 +++++++++++++++++++ ...__rules__pyupgrade__tests__UP032_3.py.snap | 4 ++++ 4 files changed, 34 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP032_3.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py new file mode 100644 index 0000000000000..9c32a1a3e27a3 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_3.py @@ -0,0 +1,9 @@ +from django.utils.translation import gettext + +long = 'long' +split_to = 'split_to' +gettext( + 'some super {} and complicated string so that the error code ' + 'E501 Triggers when this is not {} multi-line'.format( + long, split_to) +) diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 54181e3e085c5..d9ba5d890a2ba 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -28,6 +28,7 @@ mod tests { #[test_case(Rule::FString, Path::new("UP032_0.py"))] #[test_case(Rule::FString, Path::new("UP032_1.py"))] #[test_case(Rule::FString, Path::new("UP032_2.py"))] + #[test_case(Rule::FString, Path::new("UP032_3.py"))] #[test_case(Rule::FormatLiterals, Path::new("UP030_0.py"))] #[test_case(Rule::FormatLiterals, Path::new("UP030_1.py"))] #[test_case(Rule::LRUCacheWithMaxsizeNone, Path::new("UP033_0.py"))] diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index 6b3ca300d4c3e..10783a0495e43 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -328,6 +328,7 @@ pub(crate) fn f_strings( let Some(mut summary) = FormatSummaryValues::try_from_call(call, checker.locator()) else { return; }; + let mut patches: Vec<(TextRange, String)> = vec![]; let mut lex = lexer::lex_starts_at( checker.locator().slice(call.func.range()), @@ -405,6 +406,25 @@ pub(crate) fn f_strings( return; } + // Finally, avoid refactors that would introduce a runtime error. + // For example, Django's `gettext` supports `format`-style arguments, but not f-strings. + // See: https://docs.djangoproject.com/en/4.2/topics/i18n/translation + if checker.semantic().current_expressions().any(|expr| { + expr.as_call_expr().is_some_and(|call| { + checker + .semantic() + .resolve_call_path(call.func.as_ref()) + .map_or(false, |call_path| { + matches!( + call_path.as_slice(), + ["django", "utils", "translation", "gettext" | "gettext_lazy"] + ) + }) + }) + }) { + return; + } + let mut diagnostic = Diagnostic::new(FString, call.range()); // Avoid fix if there are comments within the call: diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP032_3.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP032_3.py.snap new file mode 100644 index 0000000000000..2bacb5d540775 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP032_3.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +