Preserve quotes in generated byte strings (#15778)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## Summary

This is a very closely related follow-up to #15726, adding the same
quote-preserving behavior to bytestrings. Only one rule (UP018) was
affected this time, and it was easy to mirror the plain string changes.

## Test Plan

Existing tests
This commit is contained in:
Brent Westbrook 2025-01-28 08:19:40 -05:00 committed by GitHub
parent 9c938442e5
commit 98d20a8219
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 76 additions and 35 deletions

View file

@ -1634,14 +1634,14 @@ impl Debug for ConcatenatedStringLiteral {
/// An AST node that represents either a single bytes literal or an implicitly
/// concatenated bytes literals.
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct ExprBytesLiteral {
pub range: TextRange,
pub value: BytesLiteralValue,
}
/// The value representing a [`ExprBytesLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct BytesLiteralValue {
inner: BytesLiteralValueInner,
}
@ -1714,6 +1714,16 @@ impl BytesLiteralValue {
pub fn bytes(&self) -> impl Iterator<Item = u8> + '_ {
self.iter().flat_map(|part| part.as_slice().iter().copied())
}
/// Returns the [`BytesLiteralFlags`] associated with this literal.
///
/// For an implicitly concatenated literal, it returns the flags for the first literal.
pub fn flags(&self) -> BytesLiteralFlags {
self.iter()
.next()
.expect("There should always be at least one literal in an `ExprBytesLiteral` node")
.flags
}
}
impl<'a> IntoIterator for &'a BytesLiteralValue {
@ -1771,12 +1781,6 @@ enum BytesLiteralValueInner {
Concatenated(Vec<BytesLiteral>),
}
impl Default for BytesLiteralValueInner {
fn default() -> Self {
Self::Single(BytesLiteral::default())
}
}
bitflags! {
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)]
struct BytesLiteralFlagsInner: u8 {
@ -1805,10 +1809,33 @@ bitflags! {
/// Flags that can be queried to obtain information
/// regarding the prefixes and quotes used for a bytes 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 bytes literal, consider passing along the [`BytesLiteral::flags`] field or the
/// result of the [`BytesLiteralValue::flags`] method. If you don't have an existing literal but
/// have a `Checker` from the `ruff_linter` crate available, consider using
/// `Checker::default_bytes_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 [`BytesLiteralFlags::empty`] can be used.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct BytesLiteralFlags(BytesLiteralFlagsInner);
impl BytesLiteralFlags {
/// Construct a new [`BytesLiteralFlags`] with **no flags set**.
///
/// See [`BytesLiteralFlags::with_quote_style`], [`BytesLiteralFlags::with_triple_quotes`], and
/// [`BytesLiteralFlags::with_prefix`] for ways of setting the quote style (single or double),
/// enabling triple quotes, and adding prefixes (such as `r`), respectively.
///
/// See the documentation for [`BytesLiteralFlags`] 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(BytesLiteralFlagsInner::empty())
}
#[must_use]
pub fn with_quote_style(mut self, quote_style: Quote) -> Self {
self.0
@ -1894,7 +1921,7 @@ impl fmt::Debug for BytesLiteralFlags {
/// An AST node that represents a single bytes literal which is part of an
/// [`ExprBytesLiteral`].
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct BytesLiteral {
pub range: TextRange,
pub value: Box<[u8]>,
@ -1920,7 +1947,7 @@ impl BytesLiteral {
Self {
range,
value: Box::new([]),
flags: BytesLiteralFlags::default().with_invalid(),
flags: BytesLiteralFlags::empty().with_invalid(),
}
}
}
@ -2173,7 +2200,7 @@ impl From<AnyStringFlags> for BytesLiteralFlags {
value.prefix()
)
};
let new = BytesLiteralFlags::default()
let new = BytesLiteralFlags::empty()
.with_quote_style(value.quote_style())
.with_prefix(bytestring_prefix);
if value.is_triple_quoted() {