use memchr::memchr2; pub(crate) use normalize::{normalize_string, NormalizedString, StringNormalizer}; use ruff_python_ast::str::Quote; use ruff_python_ast::{ self as ast, str_prefix::{AnyStringPrefix, StringLiteralPrefix}, AnyStringFlags, StringFlags, }; use ruff_text_size::Ranged; use crate::expression::expr_f_string::f_string_quoting; use crate::prelude::*; use crate::QuoteStyle; pub(crate) mod docstring; pub(crate) mod implicit; mod normalize; #[derive(Copy, Clone, Debug, Default)] pub(crate) enum Quoting { #[default] CanChange, Preserve, } impl Format> for AnyStringPrefix { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { // Remove the unicode prefix `u` if any because it is meaningless in Python 3+. if !matches!( self, AnyStringPrefix::Regular(StringLiteralPrefix::Empty | StringLiteralPrefix::Unicode) ) { token(self.as_str()).fmt(f)?; } Ok(()) } } #[derive(Copy, Clone, Debug)] pub(crate) struct StringQuotes { triple: bool, quote_char: Quote, } impl Format> for StringQuotes { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { let quotes = match (self.quote_char, self.triple) { (Quote::Single, false) => "'", (Quote::Single, true) => "'''", (Quote::Double, false) => "\"", (Quote::Double, true) => "\"\"\"", }; token(quotes).fmt(f) } } impl From for StringQuotes { fn from(value: AnyStringFlags) -> Self { Self { triple: value.is_triple_quoted(), quote_char: value.quote_style(), } } } impl TryFrom for Quote { type Error = (); fn try_from(style: QuoteStyle) -> Result { match style { QuoteStyle::Single => Ok(Quote::Single), QuoteStyle::Double => Ok(Quote::Double), QuoteStyle::Preserve => Err(()), } } } impl From for QuoteStyle { fn from(value: Quote) -> Self { match value { Quote::Single => QuoteStyle::Single, Quote::Double => QuoteStyle::Double, } } } // Extension trait that adds formatter specific helper methods to `StringLike`. pub(crate) trait StringLikeExtensions { fn quoting(&self, source: &str) -> Quoting; fn is_multiline(&self, source: &str) -> bool; } impl StringLikeExtensions for ast::StringLike<'_> { fn quoting(&self, source: &str) -> Quoting { match self { Self::String(_) | Self::Bytes(_) => Quoting::CanChange, Self::FString(f_string) => f_string_quoting(f_string, source), } } fn is_multiline(&self, source: &str) -> bool { match self { Self::String(_) | Self::Bytes(_) => self.parts().any(|part| { part.flags().is_triple_quoted() && memchr2(b'\n', b'\r', source[self.range()].as_bytes()).is_some() }), Self::FString(fstring) => { memchr2(b'\n', b'\r', source[fstring.range].as_bytes()).is_some() } } } }