diff --git a/Cargo.lock b/Cargo.lock index 99cc55165ad97..b8345646d05ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2345,6 +2345,7 @@ dependencies = [ "itertools 0.12.1", "lexical-parse-float", "rand", + "ruff_python_ast", "unic-ucd-category", ] diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 11c8b47aa59e6..cda951ecba299 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -44,10 +44,10 @@ use ruff_python_ast::helpers::{ }; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::name::QualifiedName; -use ruff_python_ast::str::trailing_quote; +use ruff_python_ast::str::Quote; use ruff_python_ast::visitor::{walk_except_handler, walk_f_string_element, walk_pattern, Visitor}; use ruff_python_ast::{helpers, str, visitor, PySourceType}; -use ruff_python_codegen::{Generator, Quote, Stylist}; +use ruff_python_codegen::{Generator, Stylist}; use ruff_python_index::Indexer; use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind}; use ruff_python_semantic::analyze::{imports, typing, visibility}; @@ -228,16 +228,11 @@ impl<'a> Checker<'a> { } // Find the quote character used to start the containing f-string. - let expr = self.semantic.current_expression()?; - let string_range = self.indexer.fstring_ranges().innermost(expr.start())?; - let trailing_quote = trailing_quote(self.locator.slice(string_range))?; - - // Invert the quote character, if it's a single quote. - match trailing_quote { - "'" => Some(Quote::Double), - "\"" => Some(Quote::Single), - _ => None, - } + let ast::ExprFString { value, .. } = self + .semantic + .current_expressions() + .find_map(|expr| expr.as_f_string_expr())?; + Some(value.iter().next()?.quote_style().opposite()) } /// Returns the [`SourceRow`] for the given offset. diff --git a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs index 31160302f033b..5e0c93beadab0 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/settings.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/settings.rs @@ -22,11 +22,11 @@ impl Default for Quote { } } -impl From for Quote { - fn from(value: ruff_python_ast::str::QuoteStyle) -> Self { +impl From for Quote { + fn from(value: ruff_python_ast::str::Quote) -> Self { match value { - ruff_python_ast::str::QuoteStyle::Double => Self::Double, - ruff_python_ast::str::QuoteStyle::Single => Self::Single, + ruff_python_ast::str::Quote::Double => Self::Double, + ruff_python_ast::str::Quote::Single => Self::Single, } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs index 5e28786cf6521..5109c8b86303f 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/triple_quotes.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_codegen::Quote; +use ruff_python_ast::str::Quote; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 89133331f1719..4644164bb5a52 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::{int, str::QuoteStyle, LiteralExpressionRef}; +use crate::{int, str::Quote, LiteralExpressionRef}; /// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod) #[derive(Clone, Debug, PartialEq, is_macro::Is)] @@ -1159,6 +1159,15 @@ pub enum FStringPart { FString(FString), } +impl FStringPart { + pub fn quote_style(&self) -> Quote { + match self { + Self::Literal(string_literal) => string_literal.flags.quote_style(), + Self::FString(f_string) => f_string.flags.quote_style(), + } + } +} + impl Ranged for FStringPart { fn range(&self) -> TextRange { match self { @@ -1221,11 +1230,11 @@ impl FStringFlags { } /// Does the f-string use single or double quotes in its opener and closer? - pub const fn quote_style(self) -> QuoteStyle { + pub const fn quote_style(self) -> Quote { if self.0.contains(FStringFlagsInner::DOUBLE) { - QuoteStyle::Double + Quote::Double } else { - QuoteStyle::Single + Quote::Single } } } @@ -1535,11 +1544,11 @@ impl StringLiteralFlags { } /// Does the string use single or double quotes in its opener and closer? - pub const fn quote_style(self) -> QuoteStyle { + pub const fn quote_style(self) -> Quote { if self.0.contains(StringLiteralFlagsInner::DOUBLE) { - QuoteStyle::Double + Quote::Double } else { - QuoteStyle::Single + Quote::Single } } @@ -1864,11 +1873,11 @@ impl BytesLiteralFlags { } /// Does the bytestring use single or double quotes in its opener and closer? - pub const fn quote_style(self) -> QuoteStyle { + pub const fn quote_style(self) -> Quote { if self.0.contains(BytesLiteralFlagsInner::DOUBLE) { - QuoteStyle::Double + Quote::Double } else { - QuoteStyle::Single + Quote::Single } } } diff --git a/crates/ruff_python_ast/src/str.rs b/crates/ruff_python_ast/src/str.rs index 7f1c6777c40a4..220df07857f70 100644 --- a/crates/ruff_python_ast/src/str.rs +++ b/crates/ruff_python_ast/src/str.rs @@ -1,18 +1,23 @@ +use std::fmt; + use aho_corasick::{AhoCorasick, AhoCorasickKind, Anchored, Input, MatchKind, StartKind}; use once_cell::sync::Lazy; use ruff_text_size::{TextLen, TextRange}; +/// Enumeration of the two kinds of quotes that can be used +/// for Python string/f-string/bytestring literals #[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, is_macro::Is)] -pub enum QuoteStyle { - /// E.g. ' +pub enum Quote { + /// E.g. `'` Single, - /// E.g. " + /// E.g. `"` #[default] Double, } -impl QuoteStyle { +impl Quote { + #[inline] pub const fn as_char(self) -> char { match self { Self::Single => '\'', @@ -21,12 +26,39 @@ impl QuoteStyle { } #[must_use] + #[inline] pub const fn opposite(self) -> Self { match self { Self::Single => Self::Double, Self::Double => Self::Single, } } + + #[inline] + pub const fn as_byte(self) -> u8 { + match self { + Self::Single => b'\'', + Self::Double => b'"', + } + } +} + +impl fmt::Display for Quote { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_char()) + } +} + +impl TryFrom for Quote { + type Error = (); + + fn try_from(value: char) -> Result { + match value { + '\'' => Ok(Quote::Single), + '"' => Ok(Quote::Double), + _ => Err(()), + } + } } /// Includes all permutations of `r`, `u`, `f`, and `fr` (`ur` is invalid, as is `uf`). This diff --git a/crates/ruff_python_codegen/src/generator.rs b/crates/ruff_python_codegen/src/generator.rs index 64d27ee55037e..01f7449a3aa87 100644 --- a/crates/ruff_python_codegen/src/generator.rs +++ b/crates/ruff_python_codegen/src/generator.rs @@ -2,6 +2,7 @@ use std::ops::Deref; +use ruff_python_ast::str::Quote; use ruff_python_ast::{ self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern, @@ -12,7 +13,7 @@ use ruff_python_ast::{ParameterWithDefault, TypeParams}; use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape}; use ruff_source_file::LineEnding; -use super::stylist::{Indentation, Quote, Stylist}; +use super::stylist::{Indentation, Stylist}; mod precedence { pub(crate) const NAMED_EXPR: u8 = 1; @@ -150,7 +151,7 @@ impl<'a> Generator<'a> { } fn p_bytes_repr(&mut self, s: &[u8]) { - let escape = AsciiEscape::with_preferred_quote(s, self.quote.into()); + let escape = AsciiEscape::with_preferred_quote(s, self.quote); if let Some(len) = escape.layout().len { self.buffer.reserve(len); } @@ -158,7 +159,7 @@ impl<'a> Generator<'a> { } fn p_str_repr(&mut self, s: &str) { - let escape = UnicodeEscape::with_preferred_quote(s, self.quote.into()); + let escape = UnicodeEscape::with_preferred_quote(s, self.quote); if let Some(len) = escape.layout().len { self.buffer.reserve(len); } @@ -1373,14 +1374,8 @@ impl<'a> Generator<'a> { self.unparse_f_string_body(values); } else { self.p("f"); - let mut generator = Generator::new( - self.indent, - match self.quote { - Quote::Single => Quote::Double, - Quote::Double => Quote::Single, - }, - self.line_ending, - ); + let mut generator = + Generator::new(self.indent, self.quote.opposite(), self.line_ending); generator.unparse_f_string_body(values); let body = &generator.buffer; self.p_str_repr(body); @@ -1406,11 +1401,11 @@ impl<'a> Generator<'a> { #[cfg(test)] mod tests { - use ruff_python_ast::{Mod, ModModule}; + use ruff_python_ast::{str::Quote, Mod, ModModule}; use ruff_python_parser::{self, parse_suite, Mode}; use ruff_source_file::LineEnding; - use crate::stylist::{Indentation, Quote}; + use crate::stylist::Indentation; use super::Generator; diff --git a/crates/ruff_python_codegen/src/lib.rs b/crates/ruff_python_codegen/src/lib.rs index de55f0435eb82..baa71ea1278fb 100644 --- a/crates/ruff_python_codegen/src/lib.rs +++ b/crates/ruff_python_codegen/src/lib.rs @@ -4,7 +4,7 @@ mod stylist; pub use generator::Generator; use ruff_python_parser::{lexer, parse_suite, Mode, ParseError}; use ruff_source_file::Locator; -pub use stylist::{Quote, Stylist}; +pub use stylist::Stylist; /// Run round-trip source code generation on a given Python code. pub fn round_trip(code: &str) -> Result { diff --git a/crates/ruff_python_codegen/src/stylist.rs b/crates/ruff_python_codegen/src/stylist.rs index b6b3b1a64fdd2..ffaed80f543f2 100644 --- a/crates/ruff_python_codegen/src/stylist.rs +++ b/crates/ruff_python_codegen/src/stylist.rs @@ -1,15 +1,13 @@ //! Detect code style from Python source code. -use std::fmt; use std::ops::Deref; use once_cell::unsync::OnceCell; -use ruff_python_literal::escape::Quote as StrQuote; + +use ruff_python_ast::str::Quote; use ruff_python_parser::lexer::LexResult; use ruff_python_parser::Tok; -use ruff_source_file::{find_newline, LineEnding}; - -use ruff_source_file::Locator; +use ruff_source_file::{find_newline, LineEnding, Locator}; #[derive(Debug, Clone)] pub struct Stylist<'a> { @@ -52,10 +50,8 @@ impl<'a> Stylist<'a> { fn detect_quote(tokens: &[LexResult]) -> Quote { for (token, _) in tokens.iter().flatten() { match token { - Tok::String { kind, .. } if !kind.is_triple_quoted() => { - return kind.quote_style().into() - } - Tok::FStringStart(kind) => return kind.quote_style().into(), + Tok::String { kind, .. } if !kind.is_triple_quoted() => return kind.quote_style(), + Tok::FStringStart(kind) => return kind.quote_style(), _ => continue, } } @@ -94,50 +90,6 @@ fn detect_indention(tokens: &[LexResult], locator: &Locator) -> Indentation { } } -/// The quotation style used in Python source code. -#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] -pub enum Quote { - Single, - #[default] - Double, -} - -impl From for Quote { - fn from(value: ruff_python_ast::str::QuoteStyle) -> Self { - match value { - ruff_python_ast::str::QuoteStyle::Double => Self::Double, - ruff_python_ast::str::QuoteStyle::Single => Self::Single, - } - } -} - -impl From for char { - fn from(val: Quote) -> Self { - match val { - Quote::Single => '\'', - Quote::Double => '"', - } - } -} - -impl From for StrQuote { - fn from(val: Quote) -> Self { - match val { - Quote::Single => StrQuote::Single, - Quote::Double => StrQuote::Double, - } - } -} - -impl fmt::Display for Quote { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Quote::Single => write!(f, "\'"), - Quote::Double => write!(f, "\""), - } - } -} - /// The indentation style used in Python source code. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Indentation(String); diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 2bbab9fbb0261..ebfdb782ff5d8 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -1,8 +1,8 @@ use crate::comments::Comments; use crate::other::f_string::FStringContext; -use crate::string::QuoteChar; use crate::PyFormatOptions; use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode}; +use ruff_python_ast::str::Quote; use ruff_source_file::Locator; use std::fmt::{Debug, Formatter}; use std::ops::{Deref, DerefMut}; @@ -22,7 +22,7 @@ pub struct PyFormatContext<'a> { /// works. For example, multi-line strings will always be written with a /// quote style that is inverted from the one here in order to ensure that /// the formatted Python code will be valid. - docstring: Option, + docstring: Option, /// The state of the formatter with respect to f-strings. f_string_state: FStringState, } @@ -74,7 +74,7 @@ impl<'a> PyFormatContext<'a> { /// /// The quote character returned corresponds to the quoting used for the /// docstring containing the code snippet currently being formatted. - pub(crate) fn docstring(&self) -> Option { + pub(crate) fn docstring(&self) -> Option { self.docstring } @@ -83,7 +83,7 @@ impl<'a> PyFormatContext<'a> { /// /// The quote character given should correspond to the quote character used /// for the docstring containing the code snippets. - pub(crate) fn in_docstring(self, quote: QuoteChar) -> PyFormatContext<'a> { + pub(crate) fn in_docstring(self, quote: Quote) -> PyFormatContext<'a> { PyFormatContext { docstring: Some(quote), ..self diff --git a/crates/ruff_python_formatter/src/string/docstring.rs b/crates/ruff_python_formatter/src/string/docstring.rs index a6b4539024ed2..2e0a0b0aa1d81 100644 --- a/crates/ruff_python_formatter/src/string/docstring.rs +++ b/crates/ruff_python_formatter/src/string/docstring.rs @@ -8,6 +8,7 @@ use std::{borrow::Cow, collections::VecDeque}; use itertools::Itertools; use ruff_formatter::printer::SourceMapGeneration; +use ruff_python_ast::str::Quote; use ruff_python_parser::ParseError; use {once_cell::sync::Lazy, regex::Regex}; use { @@ -19,7 +20,7 @@ use { use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError}; -use super::{NormalizedString, QuoteChar}; +use super::NormalizedString; /// Format a docstring by trimming whitespace and adjusting the indentation. /// @@ -253,7 +254,7 @@ struct DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> { already_normalized: bool, /// The quote character used by the docstring being printed. - quote_char: QuoteChar, + quote_char: Quote, /// The current code example detected in the docstring. code_example: CodeExample<'src>, @@ -550,8 +551,8 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> { // remove this check. See the `doctest_invalid_skipped` tests in // `docstring_code_examples.py` for when this check is relevant. let wrapped = match self.quote_char { - QuoteChar::Single => std::format!("'''{}'''", printed.as_code()), - QuoteChar::Double => { + Quote::Single => std::format!("'''{}'''", printed.as_code()), + Quote::Double => { std::format!(r#""""{}""""#, printed.as_code()) } }; @@ -1542,7 +1543,7 @@ enum CodeExampleAddAction<'src> { /// inside of a docstring. fn docstring_format_source( options: crate::PyFormatOptions, - docstring_quote_style: QuoteChar, + docstring_quote_style: Quote, source: &str, ) -> Result { use ruff_python_parser::AsMode; diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index 1980e1a3923f2..eb5b834f4304a 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -3,6 +3,7 @@ use bitflags::bitflags; pub(crate) use any::AnyString; pub(crate) use normalize::{normalize_string, NormalizedString, StringNormalizer}; use ruff_formatter::format_args; +use ruff_python_ast::str::Quote; use ruff_source_file::Locator; use ruff_text_size::{TextLen, TextRange, TextSize}; @@ -187,7 +188,7 @@ impl Format> for StringPrefix { #[derive(Copy, Clone, Debug)] pub(crate) struct StringQuotes { triple: bool, - quote_char: QuoteChar, + quote_char: Quote, } impl StringQuotes { @@ -195,7 +196,7 @@ impl StringQuotes { let mut chars = input.chars(); let quote_char = chars.next()?; - let quote = QuoteChar::try_from(quote_char).ok()?; + let quote = Quote::try_from(quote_char).ok()?; let triple = chars.next() == Some(quote_char) && chars.next() == Some(quote_char); @@ -221,69 +222,33 @@ impl StringQuotes { impl Format> for StringQuotes { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { let quotes = match (self.quote_char, self.triple) { - (QuoteChar::Single, false) => "'", - (QuoteChar::Single, true) => "'''", - (QuoteChar::Double, false) => "\"", - (QuoteChar::Double, true) => "\"\"\"", + (Quote::Single, false) => "'", + (Quote::Single, true) => "'''", + (Quote::Double, false) => "\"", + (Quote::Double, true) => "\"\"\"", }; token(quotes).fmt(f) } } -/// The quotation character used to quote a string, byte, or fstring literal. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum QuoteChar { - /// A single quote: `'` - Single, - - /// A double quote: '"' - Double, -} - -impl QuoteChar { - pub const fn as_char(self) -> char { - match self { - QuoteChar::Single => '\'', - QuoteChar::Double => '"', - } - } - - #[must_use] - pub const fn invert(self) -> QuoteChar { - match self { - QuoteChar::Single => QuoteChar::Double, - QuoteChar::Double => QuoteChar::Single, - } - } +impl TryFrom for Quote { + type Error = (); - #[must_use] - pub const fn from_style(style: QuoteStyle) -> Option { + fn try_from(style: QuoteStyle) -> Result { match style { - QuoteStyle::Single => Some(QuoteChar::Single), - QuoteStyle::Double => Some(QuoteChar::Double), - QuoteStyle::Preserve => None, - } - } -} - -impl From for QuoteStyle { - fn from(value: QuoteChar) -> Self { - match value { - QuoteChar::Single => QuoteStyle::Single, - QuoteChar::Double => QuoteStyle::Double, + QuoteStyle::Single => Ok(Quote::Single), + QuoteStyle::Double => Ok(Quote::Double), + QuoteStyle::Preserve => Err(()), } } } -impl TryFrom for QuoteChar { - type Error = (); - - fn try_from(value: char) -> Result { +impl From for QuoteStyle { + fn from(value: Quote) -> Self { match value { - '\'' => Ok(QuoteChar::Single), - '"' => Ok(QuoteChar::Double), - _ => Err(()), + Quote::Single => QuoteStyle::Single, + Quote::Double => QuoteStyle::Double, } } } diff --git a/crates/ruff_python_formatter/src/string/normalize.rs b/crates/ruff_python_formatter/src/string/normalize.rs index 18030528020a1..7af07597c01ef 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::iter::FusedIterator; use ruff_formatter::FormatContext; +use ruff_python_ast::str::Quote; use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; @@ -9,13 +10,13 @@ use crate::context::FStringState; use crate::options::PythonVersion; use crate::prelude::*; use crate::preview::is_f_string_formatting_enabled; -use crate::string::{QuoteChar, Quoting, StringPart, StringPrefix, StringQuotes}; +use crate::string::{Quoting, StringPart, StringPrefix, StringQuotes}; use crate::QuoteStyle; pub(crate) struct StringNormalizer { quoting: Quoting, preferred_quote_style: QuoteStyle, - parent_docstring_quote_char: Option, + parent_docstring_quote_char: Option, f_string_state: FStringState, target_version: PythonVersion, format_fstring: bool, @@ -130,7 +131,7 @@ impl StringNormalizer { // style from what the parent ultimately decided upon works, even // if it doesn't have perfect alignment with PEP8. if let Some(quote) = self.parent_docstring_quote_char { - QuoteStyle::from(quote.invert()) + QuoteStyle::from(quote.opposite()) } else if self.preferred_quote_style.is_preserve() { QuoteStyle::Preserve } else { @@ -140,7 +141,7 @@ impl StringNormalizer { self.preferred_quote_style }; - if let Some(preferred_quote) = QuoteChar::from_style(preferred_style) { + if let Ok(preferred_quote) = Quote::try_from(preferred_style) { if let Some(first_quote_or_normalized_char_offset) = first_quote_or_normalized_char_offset { @@ -281,7 +282,7 @@ impl Format> for NormalizedString<'_> { fn choose_quotes_for_raw_string( input: &str, quotes: StringQuotes, - preferred_quote: QuoteChar, + preferred_quote: Quote, ) -> StringQuotes { let preferred_quote_char = preferred_quote.as_char(); let mut chars = input.chars().peekable(); @@ -337,11 +338,7 @@ fn choose_quotes_for_raw_string( /// For triple quoted strings, the preferred quote style is always used, unless the string contains /// a triplet of the quote character (e.g., if double quotes are preferred, double quotes will be /// used unless the string contains `"""`). -fn choose_quotes_impl( - input: &str, - quotes: StringQuotes, - preferred_quote: QuoteChar, -) -> StringQuotes { +fn choose_quotes_impl(input: &str, quotes: StringQuotes, preferred_quote: Quote) -> StringQuotes { let quote = if quotes.triple { // True if the string contains a triple quote sequence of the configured quote style. let mut uses_triple_quotes = false; @@ -419,18 +416,18 @@ fn choose_quotes_impl( } match preferred_quote { - QuoteChar::Single => { + Quote::Single => { if single_quotes > double_quotes { - QuoteChar::Double + Quote::Double } else { - QuoteChar::Single + Quote::Single } } - QuoteChar::Double => { + Quote::Double => { if double_quotes > single_quotes { - QuoteChar::Single + Quote::Single } else { - QuoteChar::Double + Quote::Double } } } @@ -462,7 +459,7 @@ pub(crate) fn normalize_string( let quote = quotes.quote_char; let preferred_quote = quote.as_char(); - let opposite_quote = quote.invert().as_char(); + let opposite_quote = quote.opposite().as_char(); let mut chars = CharIndicesWithOffset::new(input, start_offset).peekable(); @@ -707,7 +704,9 @@ impl UnicodeEscape { mod tests { use std::borrow::Cow; - use crate::string::{QuoteChar, StringPrefix, StringQuotes}; + use ruff_python_ast::str::Quote; + + use crate::string::{StringPrefix, StringQuotes}; use super::{normalize_string, UnicodeEscape}; @@ -730,7 +729,7 @@ mod tests { 0, StringQuotes { triple: false, - quote_char: QuoteChar::Double, + quote_char: Quote::Double, }, StringPrefix::BYTE, true, diff --git a/crates/ruff_python_literal/Cargo.toml b/crates/ruff_python_literal/Cargo.toml index 155ac57bbeb8f..905aa3e58e443 100644 --- a/crates/ruff_python_literal/Cargo.toml +++ b/crates/ruff_python_literal/Cargo.toml @@ -15,6 +15,8 @@ license = { workspace = true } doctest = false [dependencies] +ruff_python_ast = { path = "../ruff_python_ast" } + bitflags = { workspace = true } hexf-parse = { workspace = true } is-macro = { workspace = true } diff --git a/crates/ruff_python_literal/src/escape.rs b/crates/ruff_python_literal/src/escape.rs index 03fe3b9060f32..5d6abbf671612 100644 --- a/crates/ruff_python_literal/src/escape.rs +++ b/crates/ruff_python_literal/src/escape.rs @@ -1,35 +1,4 @@ -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, is_macro::Is)] -pub enum Quote { - Single, - Double, -} - -impl Quote { - #[inline] - #[must_use] - pub const fn swap(self) -> Self { - match self { - Quote::Single => Quote::Double, - Quote::Double => Quote::Single, - } - } - - #[inline] - pub const fn to_byte(&self) -> u8 { - match self { - Quote::Single => b'\'', - Quote::Double => b'"', - } - } - - #[inline] - pub const fn to_char(&self) -> char { - match self { - Quote::Single => '\'', - Quote::Double => '"', - } - } -} +use ruff_python_ast::str::Quote; pub struct EscapeLayout { pub quote: Quote, @@ -69,7 +38,7 @@ pub(crate) const fn choose_quote( // always use primary unless we have primary but no secondary let use_secondary = primary_count > 0 && secondary_count == 0; if use_secondary { - (preferred_quote.swap(), secondary_count) + (preferred_quote.opposite(), secondary_count) } else { (preferred_quote, primary_count) } @@ -105,7 +74,7 @@ pub struct StrRepr<'r, 'a>(&'r UnicodeEscape<'a>); impl StrRepr<'_, '_> { pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { - let quote = self.0.layout().quote.to_char(); + let quote = self.0.layout().quote.as_char(); formatter.write_char(quote)?; self.0.write_body(formatter)?; formatter.write_char(quote) @@ -216,7 +185,7 @@ impl UnicodeEscape<'_> { // unicodedata lookup just for ascii characters '\x20'..='\x7e' => { // printable ascii range - if ch == quote.to_char() || ch == '\\' { + if ch == quote.as_char() || ch == '\\' { formatter.write_char('\\')?; } formatter.write_char(ch) @@ -379,7 +348,7 @@ impl AsciiEscape<'_> { b'\r' => formatter.write_str("\\r"), 0x20..=0x7e => { // printable ascii range - if ch == quote.to_byte() || ch == b'\\' { + if ch == quote.as_byte() || ch == b'\\' { formatter.write_char('\\')?; } formatter.write_char(ch as char) @@ -416,7 +385,7 @@ pub struct BytesRepr<'r, 'a>(&'r AsciiEscape<'a>); impl BytesRepr<'_, '_> { pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { - let quote = self.0.layout().quote.to_char(); + let quote = self.0.layout().quote.as_char(); formatter.write_char('b')?; formatter.write_char(quote)?; self.0.write_body(formatter)?; diff --git a/crates/ruff_python_parser/src/string_token_flags.rs b/crates/ruff_python_parser/src/string_token_flags.rs index 18fed6a52f98e..e0454e898b397 100644 --- a/crates/ruff_python_parser/src/string_token_flags.rs +++ b/crates/ruff_python_parser/src/string_token_flags.rs @@ -2,7 +2,7 @@ use std::fmt; use bitflags::bitflags; -use ruff_python_ast::{str::QuoteStyle, StringLiteralPrefix}; +use ruff_python_ast::{str::Quote, StringLiteralPrefix}; use ruff_text_size::{TextLen, TextSize}; bitflags! { @@ -171,11 +171,11 @@ impl StringKind { } /// Does the string use single or double quotes in its opener and closer? - pub const fn quote_style(self) -> QuoteStyle { + pub const fn quote_style(self) -> Quote { if self.0.contains(StringFlags::DOUBLE) { - QuoteStyle::Double + Quote::Double } else { - QuoteStyle::Single + Quote::Single } } @@ -190,13 +190,13 @@ impl StringKind { pub const fn quote_str(self) -> &'static str { if self.is_triple_quoted() { match self.quote_style() { - QuoteStyle::Single => "'''", - QuoteStyle::Double => r#"""""#, + Quote::Single => "'''", + Quote::Double => r#"""""#, } } else { match self.quote_style() { - QuoteStyle::Single => "'", - QuoteStyle::Double => "\"", + Quote::Single => "'", + Quote::Double => "\"", } } }