Skip to content

Commit

Permalink
Unify enums used for internal representation of quoting style (#10383)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood committed Mar 13, 2024
1 parent d59433b commit c2e15f3
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 223 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 7 additions & 12 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions crates/ruff_linter/src/rules/flake8_quotes/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
29 changes: 19 additions & 10 deletions crates/ruff_python_ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}
}
}
Expand Down
40 changes: 36 additions & 4 deletions crates/ruff_python_ast/src/str.rs
Original file line number Diff line number Diff line change
@@ -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 => '\'',
Expand All @@ -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
Expand Down
21 changes: 8 additions & 13 deletions crates/ruff_python_codegen/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -150,15 +151,15 @@ 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);
}
escape.bytes_repr().write(&mut self.buffer).unwrap(); // write to string doesn't fail
}

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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
58 changes: 5 additions & 53 deletions crates/ruff_python_codegen/src/stylist.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions crates/ruff_python_formatter/src/context.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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,
}
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand Down

0 comments on commit c2e15f3

Please sign in to comment.