Preserve quote style in generated code (#15726)

## Summary

This is a first step toward fixing #7799 by using the quoting style
stored in the `flags` field on `ast::StringLiteral`s to select a quoting
style. This PR does not include support for f-strings or byte strings.

Several rules also needed small updates to pass along existing quoting
styles instead of using `StringLiteralFlags::default()`. The remaining
snapshot changes are intentional and should preserve the quotes from the
input strings.

## Test Plan

Existing tests with some accepted updates, plus a few new RUF055 tests
for raw strings.

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Brent Westbrook 2025-01-27 13:41:03 -05:00 committed by GitHub
parent e994970538
commit 9bf138c45a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 550 additions and 343 deletions

View file

@ -1221,14 +1221,14 @@ impl fmt::Debug for FStringElements {
/// An AST node that represents either a single string literal or an implicitly
/// concatenated string literals.
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct ExprStringLiteral {
pub range: TextRange,
pub value: StringLiteralValue,
}
/// The value representing a [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct StringLiteralValue {
inner: StringLiteralValueInner,
}
@ -1241,6 +1241,18 @@ impl StringLiteralValue {
}
}
/// Returns the [`StringLiteralFlags`] associated with this string literal.
///
/// For an implicitly concatenated string, it returns the flags for the first literal.
pub fn flags(&self) -> StringLiteralFlags {
self.iter()
.next()
.expect(
"There should always be at least one string literal in an `ExprStringLiteral` node",
)
.flags
}
/// Creates a new string literal with the given values that represents an
/// implicitly concatenated strings.
///
@ -1371,12 +1383,6 @@ enum StringLiteralValueInner {
Concatenated(ConcatenatedStringLiteral),
}
impl Default for StringLiteralValueInner {
fn default() -> Self {
Self::Single(StringLiteral::default())
}
}
bitflags! {
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
struct StringLiteralFlagsInner: u8 {
@ -1414,10 +1420,33 @@ bitflags! {
/// Flags that can be queried to obtain information
/// regarding the prefixes and quotes used for a string literal.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
///
/// ## Notes on usage
///
/// If you're using a `Generator` from the `ruff_python_codegen` crate to generate a lint-rule fix
/// from an existing string literal, consider passing along the [`StringLiteral::flags`] field or
/// the result of the [`StringLiteralValue::flags`] method. If you don't have an existing string but
/// have a `Checker` from the `ruff_linter` crate available, consider using
/// `Checker::default_string_flags` to create instances of this struct; this method will properly
/// handle surrounding f-strings. For usage that doesn't fit into one of these categories, the
/// public constructor [`StringLiteralFlags::empty`] can be used.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct StringLiteralFlags(StringLiteralFlagsInner);
impl StringLiteralFlags {
/// Construct a new [`StringLiteralFlags`] with **no flags set**.
///
/// See [`StringLiteralFlags::with_quote_style`], [`StringLiteralFlags::with_triple_quotes`],
/// and [`StringLiteralFlags::with_prefix`] for ways of setting the quote style (single or
/// double), enabling triple quotes, and adding prefixes (such as `r` or `u`), respectively.
///
/// See the documentation for [`StringLiteralFlags`] for additional caveats on this constructor,
/// and situations in which alternative ways to construct this struct should be used, especially
/// when writing lint rules.
pub fn empty() -> Self {
Self(StringLiteralFlagsInner::empty())
}
#[must_use]
pub fn with_quote_style(mut self, quote_style: Quote) -> Self {
self.0
@ -1520,7 +1549,7 @@ impl fmt::Debug for StringLiteralFlags {
/// An AST node that represents a single string literal which is part of an
/// [`ExprStringLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct StringLiteral {
pub range: TextRange,
pub value: Box<str>,
@ -1546,7 +1575,7 @@ impl StringLiteral {
Self {
range,
value: "".into(),
flags: StringLiteralFlags::default().with_invalid(),
flags: StringLiteralFlags::empty().with_invalid(),
}
}
}
@ -2115,7 +2144,7 @@ impl From<AnyStringFlags> for StringLiteralFlags {
value.prefix()
)
};
let new = StringLiteralFlags::default()
let new = StringLiteralFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(prefix);
if value.is_triple_quoted() {