@@ -9,7 +9,8 @@ use ruff_text_size::{Ranged, TextRange};
99use crate :: context:: FStringState ;
1010use crate :: prelude:: * ;
1111use crate :: preview:: is_f_string_formatting_enabled;
12- use crate :: string:: { Quoting , StringPart , StringQuotes } ;
12+ use crate :: string:: any:: AnyStringPart ;
13+ use crate :: string:: { Quoting , StringQuotes } ;
1314use crate :: QuoteStyle ;
1415
1516pub ( crate ) struct StringNormalizer < ' a , ' src > {
@@ -37,7 +38,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
3738 self
3839 }
3940
40- fn quoting ( & self , string : StringPart ) -> Quoting {
41+ fn quoting ( & self , string : AnyStringPart ) -> Quoting {
4142 match ( self . quoting , self . context . f_string_state ( ) ) {
4243 ( Quoting :: Preserve , _) => Quoting :: Preserve ,
4344
@@ -70,24 +71,21 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
7071 }
7172 }
7273
73- /// Computes the strings preferred quotes.
74- pub ( crate ) fn choose_quotes ( & self , string : StringPart ) -> QuoteSelection {
75- let raw_content = self . context . locator ( ) . slice ( string. content_range ( ) ) ;
76- let first_quote_or_normalized_char_offset = raw_content
77- . bytes ( )
78- . position ( |b| matches ! ( b, b'\\' | b'"' | b'\'' | b'\r' | b'{' ) ) ;
79- let string_flags = string. flags ( ) ;
80-
81- let new_kind = match self . quoting ( string) {
82- Quoting :: Preserve => string_flags,
74+ /// Determines the preferred quote style for `string`.
75+ /// The formatter should use the preferred quote style unless
76+ /// it can't because the string contains the preferred quotes OR
77+ /// it leads to more escaping.
78+ pub ( super ) fn preferred_quote_style ( & self , string : AnyStringPart ) -> QuoteStyle {
79+ match self . quoting ( string) {
80+ Quoting :: Preserve => QuoteStyle :: Preserve ,
8381 Quoting :: CanChange => {
8482 let preferred_quote_style = self
8583 . preferred_quote_style
8684 . unwrap_or ( self . context . options ( ) . quote_style ( ) ) ;
8785
8886 // Per PEP 8, always prefer double quotes for triple-quoted strings.
8987 // Except when using quote-style-preserve.
90- let preferred_style = if string_flags . is_triple_quoted ( ) {
88+ if string . flags ( ) . is_triple_quoted ( ) {
9189 // ... unless we're formatting a code snippet inside a docstring,
9290 // then we specifically want to invert our quote style to avoid
9391 // writing out invalid Python.
@@ -142,26 +140,41 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
142140 }
143141 } else {
144142 preferred_quote_style
145- } ;
146-
147- if let Ok ( preferred_quote) = Quote :: try_from ( preferred_style) {
148- if let Some ( first_quote_or_normalized_char_offset) =
149- first_quote_or_normalized_char_offset
150- {
151- let quote = QuoteMetadata :: from_str (
152- & raw_content[ first_quote_or_normalized_char_offset..] ,
153- string. flags ( ) ,
154- preferred_quote,
155- )
156- . choose ( preferred_quote) ;
157- string_flags. with_quote_style ( quote)
158- } else {
159- string_flags. with_quote_style ( preferred_quote)
160- }
161- } else {
162- string_flags
163143 }
164144 }
145+ }
146+ }
147+
148+ /// Computes the strings preferred quotes.
149+ pub ( crate ) fn choose_quotes ( & self , string : AnyStringPart ) -> QuoteSelection {
150+ let raw_content = self . context . locator ( ) . slice ( string. content_range ( ) ) ;
151+ let first_quote_or_normalized_char_offset = raw_content
152+ . bytes ( )
153+ . position ( |b| matches ! ( b, b'\\' | b'"' | b'\'' | b'\r' | b'{' ) ) ;
154+ let string_flags = string. flags ( ) ;
155+ let preferred_style = self . preferred_quote_style ( string) ;
156+
157+ let new_kind = match (
158+ Quote :: try_from ( preferred_style) ,
159+ first_quote_or_normalized_char_offset,
160+ ) {
161+ // The string contains no quotes so it's safe to use the preferred quote style
162+ ( Ok ( preferred_quote) , None ) => string_flags. with_quote_style ( preferred_quote) ,
163+
164+ // The preferred quote style is single or double quotes, and the string contains a quote or
165+ // another character that may require escaping
166+ ( Ok ( preferred_quote) , Some ( first_quote_or_normalized_char_offset) ) => {
167+ let quote = QuoteMetadata :: from_str (
168+ & raw_content[ first_quote_or_normalized_char_offset..] ,
169+ string. flags ( ) ,
170+ preferred_quote,
171+ )
172+ . choose ( preferred_quote) ;
173+ string_flags. with_quote_style ( quote)
174+ }
175+
176+ // The preferred quote style is to preserve the quotes, so let's do that.
177+ ( Err ( _) , _) => string_flags,
165178 } ;
166179
167180 QuoteSelection {
@@ -171,7 +184,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
171184 }
172185
173186 /// Computes the strings preferred quotes and normalizes its content.
174- pub ( crate ) fn normalize ( & self , string : StringPart ) -> NormalizedString < ' src > {
187+ pub ( crate ) fn normalize ( & self , string : AnyStringPart ) -> NormalizedString < ' src > {
175188 let raw_content = self . context . locator ( ) . slice ( string. content_range ( ) ) ;
176189 let quote_selection = self . choose_quotes ( string) ;
177190
@@ -224,7 +237,7 @@ pub(crate) struct QuoteMetadata {
224237/// to choose the quotes for a part.
225238impl QuoteMetadata {
226239 pub ( crate ) fn from_part (
227- part : StringPart ,
240+ part : AnyStringPart ,
228241 preferred_quote : Quote ,
229242 context : & PyFormatContext ,
230243 ) -> Self {
@@ -248,7 +261,7 @@ impl QuoteMetadata {
248261 }
249262 }
250263
251- fn choose ( & self , preferred_quote : Quote ) -> Quote {
264+ pub ( super ) fn choose ( & self , preferred_quote : Quote ) -> Quote {
252265 match self . kind {
253266 QuoteMetadataKind :: Raw { contains_preferred } => {
254267 if contains_preferred {
@@ -275,7 +288,7 @@ impl QuoteMetadata {
275288 }
276289 }
277290
278- fn merge ( self , other : & QuoteMetadata ) -> Option < QuoteMetadata > {
291+ pub ( super ) fn merge ( self , other : & QuoteMetadata ) -> Option < QuoteMetadata > {
279292 match ( self . kind , other. kind ) {
280293 (
281294 QuoteMetadataKind :: Regular {
0 commit comments