Unify enums used for internal representation of quoting style (#10383)

This commit is contained in:
Alex Waygood 2024-03-13 17:19:17 +00:00 committed by GitHub
parent d59433b12e
commit c2e15f38ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 148 additions and 228 deletions

1
Cargo.lock generated
View file

@ -2345,6 +2345,7 @@ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"lexical-parse-float", "lexical-parse-float",
"rand", "rand",
"ruff_python_ast",
"unic-ucd-category", "unic-ucd-category",
] ]

View file

@ -44,10 +44,10 @@ use ruff_python_ast::helpers::{
}; };
use ruff_python_ast::identifier::Identifier; use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::QualifiedName; 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::visitor::{walk_except_handler, walk_f_string_element, walk_pattern, Visitor};
use ruff_python_ast::{helpers, str, visitor, PySourceType}; 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_index::Indexer;
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind}; use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
use ruff_python_semantic::analyze::{imports, typing, visibility}; 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. // Find the quote character used to start the containing f-string.
let expr = self.semantic.current_expression()?; let ast::ExprFString { value, .. } = self
let string_range = self.indexer.fstring_ranges().innermost(expr.start())?; .semantic
let trailing_quote = trailing_quote(self.locator.slice(string_range))?; .current_expressions()
.find_map(|expr| expr.as_f_string_expr())?;
// Invert the quote character, if it's a single quote. Some(value.iter().next()?.quote_style().opposite())
match trailing_quote {
"'" => Some(Quote::Double),
"\"" => Some(Quote::Single),
_ => None,
}
} }
/// Returns the [`SourceRow`] for the given offset. /// Returns the [`SourceRow`] for the given offset.

View file

@ -22,11 +22,11 @@ impl Default for Quote {
} }
} }
impl From<ruff_python_ast::str::QuoteStyle> for Quote { impl From<ruff_python_ast::str::Quote> for Quote {
fn from(value: ruff_python_ast::str::QuoteStyle) -> Self { fn from(value: ruff_python_ast::str::Quote) -> Self {
match value { match value {
ruff_python_ast::str::QuoteStyle::Double => Self::Double, ruff_python_ast::str::Quote::Double => Self::Double,
ruff_python_ast::str::QuoteStyle::Single => Self::Single, ruff_python_ast::str::Quote::Single => Self::Single,
} }
} }
} }

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, 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 ruff_text_size::Ranged;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;

View file

@ -11,7 +11,7 @@ use itertools::Itertools;
use ruff_text_size::{Ranged, TextRange, TextSize}; 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) /// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)
#[derive(Clone, Debug, PartialEq, is_macro::Is)] #[derive(Clone, Debug, PartialEq, is_macro::Is)]
@ -1159,6 +1159,15 @@ pub enum FStringPart {
FString(FString), 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 { impl Ranged for FStringPart {
fn range(&self) -> TextRange { fn range(&self) -> TextRange {
match self { match self {
@ -1221,11 +1230,11 @@ impl FStringFlags {
} }
/// Does the f-string use single or double quotes in its opener and closer? /// 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) { if self.0.contains(FStringFlagsInner::DOUBLE) {
QuoteStyle::Double Quote::Double
} else { } else {
QuoteStyle::Single Quote::Single
} }
} }
} }
@ -1535,11 +1544,11 @@ impl StringLiteralFlags {
} }
/// Does the string use single or double quotes in its opener and closer? /// 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) { if self.0.contains(StringLiteralFlagsInner::DOUBLE) {
QuoteStyle::Double Quote::Double
} else { } else {
QuoteStyle::Single Quote::Single
} }
} }
@ -1864,11 +1873,11 @@ impl BytesLiteralFlags {
} }
/// Does the bytestring use single or double quotes in its opener and closer? /// 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) { if self.0.contains(BytesLiteralFlagsInner::DOUBLE) {
QuoteStyle::Double Quote::Double
} else { } else {
QuoteStyle::Single Quote::Single
} }
} }
} }

View file

@ -1,18 +1,23 @@
use std::fmt;
use aho_corasick::{AhoCorasick, AhoCorasickKind, Anchored, Input, MatchKind, StartKind}; use aho_corasick::{AhoCorasick, AhoCorasickKind, Anchored, Input, MatchKind, StartKind};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use ruff_text_size::{TextLen, TextRange}; 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)] #[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq, is_macro::Is)]
pub enum QuoteStyle { pub enum Quote {
/// E.g. ' /// E.g. `'`
Single, Single,
/// E.g. " /// E.g. `"`
#[default] #[default]
Double, Double,
} }
impl QuoteStyle { impl Quote {
#[inline]
pub const fn as_char(self) -> char { pub const fn as_char(self) -> char {
match self { match self {
Self::Single => '\'', Self::Single => '\'',
@ -21,12 +26,39 @@ impl QuoteStyle {
} }
#[must_use] #[must_use]
#[inline]
pub const fn opposite(self) -> Self { pub const fn opposite(self) -> Self {
match self { match self {
Self::Single => Self::Double, Self::Single => Self::Double,
Self::Double => Self::Single, 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<char> for Quote {
type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> {
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 /// Includes all permutations of `r`, `u`, `f`, and `fr` (`ur` is invalid, as is `uf`). This

View file

@ -2,6 +2,7 @@
use std::ops::Deref; use std::ops::Deref;
use ruff_python_ast::str::Quote;
use ruff_python_ast::{ use ruff_python_ast::{
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText, self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText,
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern, 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_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
use ruff_source_file::LineEnding; use ruff_source_file::LineEnding;
use super::stylist::{Indentation, Quote, Stylist}; use super::stylist::{Indentation, Stylist};
mod precedence { mod precedence {
pub(crate) const NAMED_EXPR: u8 = 1; pub(crate) const NAMED_EXPR: u8 = 1;
@ -150,7 +151,7 @@ impl<'a> Generator<'a> {
} }
fn p_bytes_repr(&mut self, s: &[u8]) { 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 { if let Some(len) = escape.layout().len {
self.buffer.reserve(len); self.buffer.reserve(len);
} }
@ -158,7 +159,7 @@ impl<'a> Generator<'a> {
} }
fn p_str_repr(&mut self, s: &str) { 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 { if let Some(len) = escape.layout().len {
self.buffer.reserve(len); self.buffer.reserve(len);
} }
@ -1373,14 +1374,8 @@ impl<'a> Generator<'a> {
self.unparse_f_string_body(values); self.unparse_f_string_body(values);
} else { } else {
self.p("f"); self.p("f");
let mut generator = Generator::new( let mut generator =
self.indent, Generator::new(self.indent, self.quote.opposite(), self.line_ending);
match self.quote {
Quote::Single => Quote::Double,
Quote::Double => Quote::Single,
},
self.line_ending,
);
generator.unparse_f_string_body(values); generator.unparse_f_string_body(values);
let body = &generator.buffer; let body = &generator.buffer;
self.p_str_repr(body); self.p_str_repr(body);
@ -1406,11 +1401,11 @@ impl<'a> Generator<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { 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_python_parser::{self, parse_suite, Mode};
use ruff_source_file::LineEnding; use ruff_source_file::LineEnding;
use crate::stylist::{Indentation, Quote}; use crate::stylist::Indentation;
use super::Generator; use super::Generator;

View file

@ -4,7 +4,7 @@ mod stylist;
pub use generator::Generator; pub use generator::Generator;
use ruff_python_parser::{lexer, parse_suite, Mode, ParseError}; use ruff_python_parser::{lexer, parse_suite, Mode, ParseError};
use ruff_source_file::Locator; 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. /// Run round-trip source code generation on a given Python code.
pub fn round_trip(code: &str) -> Result<String, ParseError> { pub fn round_trip(code: &str) -> Result<String, ParseError> {

View file

@ -1,15 +1,13 @@
//! Detect code style from Python source code. //! Detect code style from Python source code.
use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use once_cell::unsync::OnceCell; 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::lexer::LexResult;
use ruff_python_parser::Tok; use ruff_python_parser::Tok;
use ruff_source_file::{find_newline, LineEnding}; use ruff_source_file::{find_newline, LineEnding, Locator};
use ruff_source_file::Locator;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Stylist<'a> { pub struct Stylist<'a> {
@ -52,10 +50,8 @@ impl<'a> Stylist<'a> {
fn detect_quote(tokens: &[LexResult]) -> Quote { fn detect_quote(tokens: &[LexResult]) -> Quote {
for (token, _) in tokens.iter().flatten() { for (token, _) in tokens.iter().flatten() {
match token { match token {
Tok::String { kind, .. } if !kind.is_triple_quoted() => { Tok::String { kind, .. } if !kind.is_triple_quoted() => return kind.quote_style(),
return kind.quote_style().into() Tok::FStringStart(kind) => return kind.quote_style(),
}
Tok::FStringStart(kind) => return kind.quote_style().into(),
_ => continue, _ => 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<ruff_python_ast::str::QuoteStyle> 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<Quote> for char {
fn from(val: Quote) -> Self {
match val {
Quote::Single => '\'',
Quote::Double => '"',
}
}
}
impl From<Quote> 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. /// The indentation style used in Python source code.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Indentation(String); pub struct Indentation(String);

View file

@ -1,8 +1,8 @@
use crate::comments::Comments; use crate::comments::Comments;
use crate::other::f_string::FStringContext; use crate::other::f_string::FStringContext;
use crate::string::QuoteChar;
use crate::PyFormatOptions; use crate::PyFormatOptions;
use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode}; use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode};
use ruff_python_ast::str::Quote;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::ops::{Deref, DerefMut}; 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 /// 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 /// quote style that is inverted from the one here in order to ensure that
/// the formatted Python code will be valid. /// the formatted Python code will be valid.
docstring: Option<QuoteChar>, docstring: Option<Quote>,
/// The state of the formatter with respect to f-strings. /// The state of the formatter with respect to f-strings.
f_string_state: FStringState, f_string_state: FStringState,
} }
@ -74,7 +74,7 @@ impl<'a> PyFormatContext<'a> {
/// ///
/// The quote character returned corresponds to the quoting used for the /// The quote character returned corresponds to the quoting used for the
/// docstring containing the code snippet currently being formatted. /// docstring containing the code snippet currently being formatted.
pub(crate) fn docstring(&self) -> Option<QuoteChar> { pub(crate) fn docstring(&self) -> Option<Quote> {
self.docstring self.docstring
} }
@ -83,7 +83,7 @@ impl<'a> PyFormatContext<'a> {
/// ///
/// The quote character given should correspond to the quote character used /// The quote character given should correspond to the quote character used
/// for the docstring containing the code snippets. /// 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 { PyFormatContext {
docstring: Some(quote), docstring: Some(quote),
..self ..self

View file

@ -8,6 +8,7 @@ use std::{borrow::Cow, collections::VecDeque};
use itertools::Itertools; use itertools::Itertools;
use ruff_formatter::printer::SourceMapGeneration; use ruff_formatter::printer::SourceMapGeneration;
use ruff_python_ast::str::Quote;
use ruff_python_parser::ParseError; use ruff_python_parser::ParseError;
use {once_cell::sync::Lazy, regex::Regex}; use {once_cell::sync::Lazy, regex::Regex};
use { use {
@ -19,7 +20,7 @@ use {
use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError}; use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError};
use super::{NormalizedString, QuoteChar}; use super::NormalizedString;
/// Format a docstring by trimming whitespace and adjusting the indentation. /// Format a docstring by trimming whitespace and adjusting the indentation.
/// ///
@ -253,7 +254,7 @@ struct DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
already_normalized: bool, already_normalized: bool,
/// The quote character used by the docstring being printed. /// The quote character used by the docstring being printed.
quote_char: QuoteChar, quote_char: Quote,
/// The current code example detected in the docstring. /// The current code example detected in the docstring.
code_example: CodeExample<'src>, 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 // remove this check. See the `doctest_invalid_skipped` tests in
// `docstring_code_examples.py` for when this check is relevant. // `docstring_code_examples.py` for when this check is relevant.
let wrapped = match self.quote_char { let wrapped = match self.quote_char {
QuoteChar::Single => std::format!("'''{}'''", printed.as_code()), Quote::Single => std::format!("'''{}'''", printed.as_code()),
QuoteChar::Double => { Quote::Double => {
std::format!(r#""""{}""""#, printed.as_code()) std::format!(r#""""{}""""#, printed.as_code())
} }
}; };
@ -1542,7 +1543,7 @@ enum CodeExampleAddAction<'src> {
/// inside of a docstring. /// inside of a docstring.
fn docstring_format_source( fn docstring_format_source(
options: crate::PyFormatOptions, options: crate::PyFormatOptions,
docstring_quote_style: QuoteChar, docstring_quote_style: Quote,
source: &str, source: &str,
) -> Result<Printed, FormatModuleError> { ) -> Result<Printed, FormatModuleError> {
use ruff_python_parser::AsMode; use ruff_python_parser::AsMode;

View file

@ -3,6 +3,7 @@ use bitflags::bitflags;
pub(crate) use any::AnyString; pub(crate) use any::AnyString;
pub(crate) use normalize::{normalize_string, NormalizedString, StringNormalizer}; pub(crate) use normalize::{normalize_string, NormalizedString, StringNormalizer};
use ruff_formatter::format_args; use ruff_formatter::format_args;
use ruff_python_ast::str::Quote;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{TextLen, TextRange, TextSize}; use ruff_text_size::{TextLen, TextRange, TextSize};
@ -187,7 +188,7 @@ impl Format<PyFormatContext<'_>> for StringPrefix {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub(crate) struct StringQuotes { pub(crate) struct StringQuotes {
triple: bool, triple: bool,
quote_char: QuoteChar, quote_char: Quote,
} }
impl StringQuotes { impl StringQuotes {
@ -195,7 +196,7 @@ impl StringQuotes {
let mut chars = input.chars(); let mut chars = input.chars();
let quote_char = chars.next()?; 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); let triple = chars.next() == Some(quote_char) && chars.next() == Some(quote_char);
@ -221,69 +222,33 @@ impl StringQuotes {
impl Format<PyFormatContext<'_>> for StringQuotes { impl Format<PyFormatContext<'_>> for StringQuotes {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let quotes = match (self.quote_char, self.triple) { let quotes = match (self.quote_char, self.triple) {
(QuoteChar::Single, false) => "'", (Quote::Single, false) => "'",
(QuoteChar::Single, true) => "'''", (Quote::Single, true) => "'''",
(QuoteChar::Double, false) => "\"", (Quote::Double, false) => "\"",
(QuoteChar::Double, true) => "\"\"\"", (Quote::Double, true) => "\"\"\"",
}; };
token(quotes).fmt(f) token(quotes).fmt(f)
} }
} }
/// The quotation character used to quote a string, byte, or fstring literal. impl TryFrom<QuoteStyle> for Quote {
#[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,
}
}
#[must_use]
pub const fn from_style(style: QuoteStyle) -> Option<QuoteChar> {
match style {
QuoteStyle::Single => Some(QuoteChar::Single),
QuoteStyle::Double => Some(QuoteChar::Double),
QuoteStyle::Preserve => None,
}
}
}
impl From<QuoteChar> for QuoteStyle {
fn from(value: QuoteChar) -> Self {
match value {
QuoteChar::Single => QuoteStyle::Single,
QuoteChar::Double => QuoteStyle::Double,
}
}
}
impl TryFrom<char> for QuoteChar {
type Error = (); type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> { fn try_from(style: QuoteStyle) -> Result<Quote, ()> {
match value { match style {
'\'' => Ok(QuoteChar::Single), QuoteStyle::Single => Ok(Quote::Single),
'"' => Ok(QuoteChar::Double), QuoteStyle::Double => Ok(Quote::Double),
_ => Err(()), QuoteStyle::Preserve => Err(()),
}
}
}
impl From<Quote> for QuoteStyle {
fn from(value: Quote) -> Self {
match value {
Quote::Single => QuoteStyle::Single,
Quote::Double => QuoteStyle::Double,
} }
} }
} }

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use ruff_formatter::FormatContext; use ruff_formatter::FormatContext;
use ruff_python_ast::str::Quote;
use ruff_source_file::Locator; use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
@ -9,13 +10,13 @@ use crate::context::FStringState;
use crate::options::PythonVersion; use crate::options::PythonVersion;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_f_string_formatting_enabled; 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; use crate::QuoteStyle;
pub(crate) struct StringNormalizer { pub(crate) struct StringNormalizer {
quoting: Quoting, quoting: Quoting,
preferred_quote_style: QuoteStyle, preferred_quote_style: QuoteStyle,
parent_docstring_quote_char: Option<QuoteChar>, parent_docstring_quote_char: Option<Quote>,
f_string_state: FStringState, f_string_state: FStringState,
target_version: PythonVersion, target_version: PythonVersion,
format_fstring: bool, format_fstring: bool,
@ -130,7 +131,7 @@ impl StringNormalizer {
// style from what the parent ultimately decided upon works, even // style from what the parent ultimately decided upon works, even
// if it doesn't have perfect alignment with PEP8. // if it doesn't have perfect alignment with PEP8.
if let Some(quote) = self.parent_docstring_quote_char { 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() { } else if self.preferred_quote_style.is_preserve() {
QuoteStyle::Preserve QuoteStyle::Preserve
} else { } else {
@ -140,7 +141,7 @@ impl StringNormalizer {
self.preferred_quote_style 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) = if let Some(first_quote_or_normalized_char_offset) =
first_quote_or_normalized_char_offset first_quote_or_normalized_char_offset
{ {
@ -281,7 +282,7 @@ impl Format<PyFormatContext<'_>> for NormalizedString<'_> {
fn choose_quotes_for_raw_string( fn choose_quotes_for_raw_string(
input: &str, input: &str,
quotes: StringQuotes, quotes: StringQuotes,
preferred_quote: QuoteChar, preferred_quote: Quote,
) -> StringQuotes { ) -> StringQuotes {
let preferred_quote_char = preferred_quote.as_char(); let preferred_quote_char = preferred_quote.as_char();
let mut chars = input.chars().peekable(); 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 /// 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 /// a triplet of the quote character (e.g., if double quotes are preferred, double quotes will be
/// used unless the string contains `"""`). /// used unless the string contains `"""`).
fn choose_quotes_impl( fn choose_quotes_impl(input: &str, quotes: StringQuotes, preferred_quote: Quote) -> StringQuotes {
input: &str,
quotes: StringQuotes,
preferred_quote: QuoteChar,
) -> StringQuotes {
let quote = if quotes.triple { let quote = if quotes.triple {
// True if the string contains a triple quote sequence of the configured quote style. // True if the string contains a triple quote sequence of the configured quote style.
let mut uses_triple_quotes = false; let mut uses_triple_quotes = false;
@ -419,18 +416,18 @@ fn choose_quotes_impl(
} }
match preferred_quote { match preferred_quote {
QuoteChar::Single => { Quote::Single => {
if single_quotes > double_quotes { if single_quotes > double_quotes {
QuoteChar::Double Quote::Double
} else { } else {
QuoteChar::Single Quote::Single
} }
} }
QuoteChar::Double => { Quote::Double => {
if double_quotes > single_quotes { if double_quotes > single_quotes {
QuoteChar::Single Quote::Single
} else { } else {
QuoteChar::Double Quote::Double
} }
} }
} }
@ -462,7 +459,7 @@ pub(crate) fn normalize_string(
let quote = quotes.quote_char; let quote = quotes.quote_char;
let preferred_quote = quote.as_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(); let mut chars = CharIndicesWithOffset::new(input, start_offset).peekable();
@ -707,7 +704,9 @@ impl UnicodeEscape {
mod tests { mod tests {
use std::borrow::Cow; 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}; use super::{normalize_string, UnicodeEscape};
@ -730,7 +729,7 @@ mod tests {
0, 0,
StringQuotes { StringQuotes {
triple: false, triple: false,
quote_char: QuoteChar::Double, quote_char: Quote::Double,
}, },
StringPrefix::BYTE, StringPrefix::BYTE,
true, true,

View file

@ -15,6 +15,8 @@ license = { workspace = true }
doctest = false doctest = false
[dependencies] [dependencies]
ruff_python_ast = { path = "../ruff_python_ast" }
bitflags = { workspace = true } bitflags = { workspace = true }
hexf-parse = { workspace = true } hexf-parse = { workspace = true }
is-macro = { workspace = true } is-macro = { workspace = true }

View file

@ -1,35 +1,4 @@
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, is_macro::Is)] use ruff_python_ast::str::Quote;
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 => '"',
}
}
}
pub struct EscapeLayout { pub struct EscapeLayout {
pub quote: Quote, pub quote: Quote,
@ -69,7 +38,7 @@ pub(crate) const fn choose_quote(
// always use primary unless we have primary but no secondary // always use primary unless we have primary but no secondary
let use_secondary = primary_count > 0 && secondary_count == 0; let use_secondary = primary_count > 0 && secondary_count == 0;
if use_secondary { if use_secondary {
(preferred_quote.swap(), secondary_count) (preferred_quote.opposite(), secondary_count)
} else { } else {
(preferred_quote, primary_count) (preferred_quote, primary_count)
} }
@ -105,7 +74,7 @@ pub struct StrRepr<'r, 'a>(&'r UnicodeEscape<'a>);
impl StrRepr<'_, '_> { impl StrRepr<'_, '_> {
pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { 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)?; formatter.write_char(quote)?;
self.0.write_body(formatter)?; self.0.write_body(formatter)?;
formatter.write_char(quote) formatter.write_char(quote)
@ -216,7 +185,7 @@ impl UnicodeEscape<'_> {
// unicodedata lookup just for ascii characters // unicodedata lookup just for ascii characters
'\x20'..='\x7e' => { '\x20'..='\x7e' => {
// printable ascii range // printable ascii range
if ch == quote.to_char() || ch == '\\' { if ch == quote.as_char() || ch == '\\' {
formatter.write_char('\\')?; formatter.write_char('\\')?;
} }
formatter.write_char(ch) formatter.write_char(ch)
@ -379,7 +348,7 @@ impl AsciiEscape<'_> {
b'\r' => formatter.write_str("\\r"), b'\r' => formatter.write_str("\\r"),
0x20..=0x7e => { 0x20..=0x7e => {
// printable ascii range // printable ascii range
if ch == quote.to_byte() || ch == b'\\' { if ch == quote.as_byte() || ch == b'\\' {
formatter.write_char('\\')?; formatter.write_char('\\')?;
} }
formatter.write_char(ch as char) formatter.write_char(ch as char)
@ -416,7 +385,7 @@ pub struct BytesRepr<'r, 'a>(&'r AsciiEscape<'a>);
impl BytesRepr<'_, '_> { impl BytesRepr<'_, '_> {
pub fn write(&self, formatter: &mut impl std::fmt::Write) -> std::fmt::Result { 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('b')?;
formatter.write_char(quote)?; formatter.write_char(quote)?;
self.0.write_body(formatter)?; self.0.write_body(formatter)?;

View file

@ -2,7 +2,7 @@ use std::fmt;
use bitflags::bitflags; use bitflags::bitflags;
use ruff_python_ast::{str::QuoteStyle, StringLiteralPrefix}; use ruff_python_ast::{str::Quote, StringLiteralPrefix};
use ruff_text_size::{TextLen, TextSize}; use ruff_text_size::{TextLen, TextSize};
bitflags! { bitflags! {
@ -171,11 +171,11 @@ impl StringKind {
} }
/// Does the string use single or double quotes in its opener and closer? /// 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) { if self.0.contains(StringFlags::DOUBLE) {
QuoteStyle::Double Quote::Double
} else { } else {
QuoteStyle::Single Quote::Single
} }
} }
@ -190,13 +190,13 @@ impl StringKind {
pub const fn quote_str(self) -> &'static str { pub const fn quote_str(self) -> &'static str {
if self.is_triple_quoted() { if self.is_triple_quoted() {
match self.quote_style() { match self.quote_style() {
QuoteStyle::Single => "'''", Quote::Single => "'''",
QuoteStyle::Double => r#"""""#, Quote::Double => r#"""""#,
} }
} else { } else {
match self.quote_style() { match self.quote_style() {
QuoteStyle::Single => "'", Quote::Single => "'",
QuoteStyle::Double => "\"", Quote::Double => "\"",
} }
} }
} }