mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
Unify enums used for internal representation of quoting style (#10383)
This commit is contained in:
parent
d59433b12e
commit
c2e15f38ee
16 changed files with 148 additions and 228 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2345,6 +2345,7 @@ dependencies = [
|
|||
"itertools 0.12.1",
|
||||
"lexical-parse-float",
|
||||
"rand",
|
||||
"ruff_python_ast",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -22,11 +22,11 @@ impl Default for Quote {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ruff_python_ast::str::QuoteStyle> for Quote {
|
||||
fn from(value: ruff_python_ast::str::QuoteStyle) -> Self {
|
||||
impl From<ruff_python_ast::str::Quote> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<String, ParseError> {
|
||||
|
|
|
@ -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<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.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Indentation(String);
|
||||
|
|
|
@ -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<QuoteChar>,
|
||||
docstring: Option<Quote>,
|
||||
/// 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<QuoteChar> {
|
||||
pub(crate) fn docstring(&self) -> Option<Quote> {
|
||||
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
|
||||
|
|
|
@ -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<Printed, FormatModuleError> {
|
||||
use ruff_python_parser::AsMode;
|
||||
|
|
|
@ -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<PyFormatContext<'_>> 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<PyFormatContext<'_>> 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,
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
impl TryFrom<QuoteStyle> for Quote {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
fn try_from(style: QuoteStyle) -> Result<Quote, ()> {
|
||||
match style {
|
||||
QuoteStyle::Single => Ok(Quote::Single),
|
||||
QuoteStyle::Double => Ok(Quote::Double),
|
||||
QuoteStyle::Preserve => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quote> for QuoteStyle {
|
||||
fn from(value: Quote) -> Self {
|
||||
match value {
|
||||
'\'' => Ok(QuoteChar::Single),
|
||||
'"' => Ok(QuoteChar::Double),
|
||||
_ => Err(()),
|
||||
Quote::Single => QuoteStyle::Single,
|
||||
Quote::Double => QuoteStyle::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<QuoteChar>,
|
||||
parent_docstring_quote_char: Option<Quote>,
|
||||
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<PyFormatContext<'_>> 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,
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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 => "\"",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue