diff --git a/crates/ruff_linter/resources/test/fixtures/flynt/FLY002.py b/crates/ruff_linter/resources/test/fixtures/flynt/FLY002.py index 2252d09741..25d3c65ae8 100644 --- a/crates/ruff_linter/resources/test/fixtures/flynt/FLY002.py +++ b/crates/ruff_linter/resources/test/fixtures/flynt/FLY002.py @@ -23,3 +23,9 @@ nok9 = '\n'.join([r"raw string", '<""">', "<'''>"]) # Not OK (both triple-quote # Regression test for: https://github.com/astral-sh/ruff/issues/7197 def create_file_public_url(url, filename): return''.join([url, filename]) + +# Regression test for: https://github.com/astral-sh/ruff/issues/19837 +nok10 = "".join((foo, '"')) +nok11 = ''.join((foo, "'")) +nok12 = ''.join([foo, "'", '"']) +nok13 = "".join([foo, "'", '"']) diff --git a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs index 5ddf21dc49..e446654457 100644 --- a/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs +++ b/crates/ruff_linter/src/rules/flynt/rules/static_join_to_fstring.rs @@ -2,7 +2,7 @@ use ast::FStringFlags; use itertools::Itertools; use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::{self as ast, Arguments, Expr, StringFlags}; +use ruff_python_ast::{self as ast, Arguments, Expr, StringFlags, str::Quote}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -115,6 +115,8 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option< } let mut f_string_elements = Vec::with_capacity(joinees.len() * 2); + let mut has_single_quote = joiner.contains('\''); + let mut has_double_quote = joiner.contains('"'); let mut first = true; for expr in joinees { @@ -126,14 +128,31 @@ fn build_fstring(joiner: &str, joinees: &[Expr], flags: FStringFlags) -> Option< if !std::mem::take(&mut first) { f_string_elements.push(helpers::to_interpolated_string_literal_element(joiner)); } - f_string_elements.push(helpers::to_interpolated_string_element(expr)?); + let element = helpers::to_interpolated_string_element(expr)?; + if let ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { + value, + .. + }) = &element + { + has_single_quote |= value.contains('\''); + has_double_quote |= value.contains('"'); + } + f_string_elements.push(element); } + let quote = flags.quote_style(); + let adjusted_quote = match quote { + Quote::Single if has_single_quote && !has_double_quote => quote.opposite(), + Quote::Double if has_double_quote && !has_single_quote => quote.opposite(), + _ if has_double_quote && has_single_quote => return None, + _ => quote, + }; + let node = ast::FString { elements: f_string_elements.into(), range: TextRange::default(), node_index: ruff_python_ast::AtomicNodeIndex::NONE, - flags, + flags: flags.with_quote_style(adjusted_quote), }; Some(node.into()) } diff --git a/crates/ruff_linter/src/rules/flynt/snapshots/ruff_linter__rules__flynt__tests__FLY002_FLY002.py.snap b/crates/ruff_linter/src/rules/flynt/snapshots/ruff_linter__rules__flynt__tests__FLY002_FLY002.py.snap index 79d5496910..81a410947c 100644 --- a/crates/ruff_linter/src/rules/flynt/snapshots/ruff_linter__rules__flynt__tests__FLY002_FLY002.py.snap +++ b/crates/ruff_linter/src/rules/flynt/snapshots/ruff_linter__rules__flynt__tests__FLY002_FLY002.py.snap @@ -153,6 +153,8 @@ FLY002 [*] Consider `f"{url}{filename}"` instead of string join 24 | def create_file_public_url(url, filename): 25 | return''.join([url, filename]) | ^^^^^^^^^^^^^^^^^^^^^^^^ +26 | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 | help: Replace with `f"{url}{filename}"` 22 | @@ -160,4 +162,47 @@ help: Replace with `f"{url}{filename}"` 24 | def create_file_public_url(url, filename): - return''.join([url, filename]) 25 + return f"{url}{filename}" +26 | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 +28 | nok10 = "".join((foo, '"')) +note: This is an unsafe fix and may change runtime behavior + +FLY002 [*] Consider `f'{foo}"'` instead of string join + --> FLY002.py:28:9 + | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 +28 | nok10 = "".join((foo, '"')) + | ^^^^^^^^^^^^^^^^^^^ +29 | nok11 = ''.join((foo, "'")) +30 | nok12 = ''.join([foo, "'", '"']) + | +help: Replace with `f'{foo}"'` +25 | return''.join([url, filename]) +26 | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 + - nok10 = "".join((foo, '"')) +28 + nok10 = f'{foo}"' +29 | nok11 = ''.join((foo, "'")) +30 | nok12 = ''.join([foo, "'", '"']) +31 | nok13 = "".join([foo, "'", '"']) +note: This is an unsafe fix and may change runtime behavior + +FLY002 [*] Consider `f"{foo}'"` instead of string join + --> FLY002.py:29:9 + | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 +28 | nok10 = "".join((foo, '"')) +29 | nok11 = ''.join((foo, "'")) + | ^^^^^^^^^^^^^^^^^^^ +30 | nok12 = ''.join([foo, "'", '"']) +31 | nok13 = "".join([foo, "'", '"']) + | +help: Replace with `f"{foo}'"` +26 | +27 | # Regression test for: https://github.com/astral-sh/ruff/issues/19837 +28 | nok10 = "".join((foo, '"')) + - nok11 = ''.join((foo, "'")) +29 + nok11 = f"{foo}'" +30 | nok12 = ''.join([foo, "'", '"']) +31 | nok13 = "".join([foo, "'", '"']) note: This is an unsafe fix and may change runtime behavior