(string: StringLike, context: &PyFormatContext)
| 135 | /// Creates a new formatter. Returns `None` if the string can't be merged into a single string. |
| 136 | pub(crate) fn new(string: StringLike<'a>, context: &PyFormatContext) -> Option<Self> { |
| 137 | fn merge_flags(string: StringLike, context: &PyFormatContext) -> Option<AnyStringFlags> { |
| 138 | // Multiline strings can never fit on a single line. |
| 139 | if string.is_multiline(context) { |
| 140 | return None; |
| 141 | } |
| 142 | |
| 143 | let first_part = string.parts().next()?; |
| 144 | |
| 145 | // The string is either a regular string, f-string, t-string, or bytes string. |
| 146 | let normalizer = StringNormalizer::from_context(context); |
| 147 | |
| 148 | // Some if a part requires preserving its quotes. |
| 149 | let mut preserve_quotes_requirement: Option<Quote> = None; |
| 150 | |
| 151 | // Early exit if it's known that this string can't be joined |
| 152 | for part in string.parts() { |
| 153 | // Similar to Black, don't collapse triple quoted and raw strings. |
| 154 | // We could technically join strings that are raw-strings and use the same quotes but lets not do this for now. |
| 155 | // Joining triple quoted strings is more complicated because an |
| 156 | // implicit concatenated string could become a docstring (if it's the first string in a block). |
| 157 | // That means the joined string formatting would have to call into |
| 158 | // the docstring formatting or otherwise guarantee that the output |
| 159 | // won't change on a second run. |
| 160 | if part.flags().is_triple_quoted() || part.flags().is_raw_string() { |
| 161 | return None; |
| 162 | } |
| 163 | |
| 164 | // For now, preserve comments documenting a specific part over possibly |
| 165 | // collapsing onto a single line. Collapsing could result in pragma comments |
| 166 | // now covering more code. |
| 167 | if context.comments().leading_trailing(&part).next().is_some() { |
| 168 | return None; |
| 169 | } |
| 170 | |
| 171 | match part { |
| 172 | StringLikePart::FString(fstring) => { |
| 173 | if matches!(string, StringLike::TString(_)) { |
| 174 | // Don't concatenate t-strings and f-strings |
| 175 | return None; |
| 176 | } |
| 177 | if context.options().target_version().supports_pep_701() { |
| 178 | if is_interpolated_string_with_quoted_format_spec_and_debug( |
| 179 | &fstring.elements, |
| 180 | fstring.flags.into(), |
| 181 | context, |
| 182 | ) { |
| 183 | if preserve_quotes_requirement |
| 184 | .is_some_and(|quote| quote != part.flags().quote_style()) |
| 185 | { |
| 186 | return None; |
| 187 | } |
| 188 | preserve_quotes_requirement = Some(part.flags().quote_style()); |
| 189 | } |
| 190 | } |
| 191 | // Avoid invalid syntax for pre Python 312: |
| 192 | // * When joining parts that have debug expressions with quotes: `f"{10 + len('bar')=}" f'{10 + len("bar")=}' |
| 193 | // * When joining parts that contain triple quoted strings with quotes: `f"{'''test ' '''}" f'{"""other " """}'` |
| 194 | else if is_fstring_with_quoted_debug_expression(fstring, context) |
nothing calls this directly
no test coverage detected