Determines the preferred quote style for `string`. The formatter should use the preferred quote style unless it can't because the string contains the preferred quotes OR it leads to more escaping. Note: If you add more cases here where we return `QuoteStyle::Preserve`, make sure to also add them to [`FormatImplicitConcatenatedStringFlat::new`](crate::string::implicit::FormatImplicitConcatenatedSt
(&self, string: StringLikePart)
| 42 | /// add them to |
| 43 | /// [`FormatImplicitConcatenatedStringFlat::new`](crate::string::implicit::FormatImplicitConcatenatedStringFlat::new). |
| 44 | pub(super) fn preferred_quote_style(&self, string: StringLikePart) -> QuoteStyle { |
| 45 | let preferred_quote_style = self |
| 46 | .preferred_quote_style |
| 47 | .unwrap_or(self.context.options().quote_style()); |
| 48 | let supports_pep_701 = self.context.options().target_version().supports_pep_701(); |
| 49 | |
| 50 | // Preserve the existing quote style for nested interpolations more than one layer deep, if |
| 51 | // PEP 701 isn't supported. |
| 52 | if !supports_pep_701 && self.context.interpolated_string_state().is_nested() { |
| 53 | return QuoteStyle::Preserve; |
| 54 | } |
| 55 | |
| 56 | // For f-strings and t-strings prefer alternating the quotes unless the outer string is |
| 57 | // triple quoted and the inner isn't. In Python 3.12+, `nested-string-quote-style = |
| 58 | // "preferred"` uses the configured quote style instead. |
| 59 | if let InterpolatedStringState::InsideInterpolatedElement(parent_context) |
| 60 | | InterpolatedStringState::NestedInterpolatedElement(parent_context) = |
| 61 | self.context.interpolated_string_state() |
| 62 | { |
| 63 | let parent_flags = parent_context.flags(); |
| 64 | let nested_string_quote_style = self.context.options().nested_string_quote_style(); |
| 65 | |
| 66 | if !parent_flags.is_triple_quoted() || string.flags().is_triple_quoted() { |
| 67 | // When `nested-string-quote-style = "preferred"` and we're targeting Python |
| 68 | // 3.12+, use the preferred quote style consistently. |
| 69 | if supports_pep_701 |
| 70 | && nested_string_quote_style.is_preferred() |
| 71 | && !preferred_quote_style.is_preserve() |
| 72 | { |
| 73 | return preferred_quote_style; |
| 74 | } |
| 75 | // Otherwise, use alternating quotes for compatibility. |
| 76 | // This logic is even necessary when using preserve and the target python version doesn't support PEP701 because |
| 77 | // we might end up joining two f-strings that have different quote styles, in which case we need to alternate the quotes |
| 78 | // for inner strings to avoid a syntax error: `string = "this is my string with " f'"{params.get("mine")}"'` |
| 79 | else if !preferred_quote_style.is_preserve() || !supports_pep_701 { |
| 80 | return QuoteStyle::from(parent_flags.quote_style().opposite()); |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Leave the quotes unchanged for all other strings. |
| 86 | if preferred_quote_style.is_preserve() { |
| 87 | return QuoteStyle::Preserve; |
| 88 | } |
| 89 | |
| 90 | // There are cases where it is necessary to preserve the quotes to prevent an invalid f-string or t-string. |
| 91 | match string { |
| 92 | StringLikePart::FString(fstring) => { |
| 93 | // There are two cases where it's necessary to preserve the quotes if the |
| 94 | // target version is pre 3.12 and the part is an f-string. |
| 95 | if !supports_pep_701 { |
| 96 | // An f-string expression contains a debug text with a quote character |
| 97 | // because the formatter will emit the debug expression **exactly** the |
| 98 | // same as in the source text. |
| 99 | if is_fstring_with_quoted_debug_expression(fstring, self.context) { |
| 100 | return QuoteStyle::Preserve; |
| 101 | } |
no test coverage detected