Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify enums used for internal representation of quoting style #10383

Merged
merged 3 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
Comment on lines -231 to +235
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice, I like this change as the current_expressions start with innermost f-string in case of nested f-strings.

}

/// 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
Loading
Loading