mirror of https://github.com/astral-sh/ruff
174 lines
4.5 KiB
Rust
174 lines
4.5 KiB
Rust
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_python_ast::{self as ast, AnyStringKind, AnyStringPrefix};
|
|
use ruff_text_size::{Ranged, TextRange};
|
|
|
|
use crate::comments::{leading_comments, trailing_comments};
|
|
use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space;
|
|
use crate::prelude::*;
|
|
use crate::QuoteStyle;
|
|
|
|
mod any;
|
|
pub(crate) mod docstring;
|
|
mod normalize;
|
|
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
pub(crate) enum Quoting {
|
|
#[default]
|
|
CanChange,
|
|
Preserve,
|
|
}
|
|
|
|
/// Formats any implicitly concatenated string. This could be any valid combination
|
|
/// of string, bytes or f-string literals.
|
|
pub(crate) struct FormatStringContinuation<'a> {
|
|
string: &'a AnyString<'a>,
|
|
}
|
|
|
|
impl<'a> FormatStringContinuation<'a> {
|
|
pub(crate) fn new(string: &'a AnyString<'a>) -> Self {
|
|
Self { string }
|
|
}
|
|
}
|
|
|
|
impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
|
let comments = f.context().comments().clone();
|
|
let quoting = self.string.quoting(&f.context().locator());
|
|
|
|
let mut joiner = f.join_with(in_parentheses_only_soft_line_break_or_space());
|
|
|
|
for part in self.string.parts(quoting) {
|
|
joiner.entry(&format_args![
|
|
line_suffix_boundary(),
|
|
leading_comments(comments.leading(&part)),
|
|
part,
|
|
trailing_comments(comments.trailing(&part))
|
|
]);
|
|
}
|
|
|
|
joiner.finish()
|
|
}
|
|
}
|
|
|
|
impl Format<PyFormatContext<'_>> for AnyStringPrefix {
|
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
|
// Remove the unicode prefix `u` if any because it is meaningless in Python 3+.
|
|
if !matches!(
|
|
self,
|
|
AnyStringPrefix::Regular(
|
|
ast::StringLiteralPrefix::Empty | ast::StringLiteralPrefix::Unicode
|
|
)
|
|
) {
|
|
token(self.as_str()).fmt(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub(crate) struct StringQuotes {
|
|
triple: bool,
|
|
quote_char: Quote,
|
|
}
|
|
|
|
impl Format<PyFormatContext<'_>> for StringQuotes {
|
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
|
let quotes = match (self.quote_char, self.triple) {
|
|
(Quote::Single, false) => "'",
|
|
(Quote::Single, true) => "'''",
|
|
(Quote::Double, false) => "\"",
|
|
(Quote::Double, true) => "\"\"\"",
|
|
};
|
|
|
|
token(quotes).fmt(f)
|
|
}
|
|
}
|
|
|
|
impl From<AnyStringKind> for StringQuotes {
|
|
fn from(value: AnyStringKind) -> Self {
|
|
Self {
|
|
triple: value.is_triple_quoted(),
|
|
quote_char: value.quote_style(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<QuoteStyle> for Quote {
|
|
type 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 {
|
|
Quote::Single => QuoteStyle::Single,
|
|
Quote::Double => QuoteStyle::Double,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub(crate) struct StringPart {
|
|
kind: AnyStringKind,
|
|
range: TextRange,
|
|
}
|
|
|
|
impl Ranged for StringPart {
|
|
fn range(&self) -> TextRange {
|
|
self.range
|
|
}
|
|
}
|
|
|
|
impl StringPart {
|
|
/// Use the `kind()` method to retrieve information about the
|
|
fn kind(self) -> AnyStringKind {
|
|
self.kind
|
|
}
|
|
|
|
/// Returns the range of the string's content in the source (minus prefix and quotes).
|
|
fn content_range(self) -> TextRange {
|
|
let kind = self.kind();
|
|
TextRange::new(
|
|
self.start() + kind.opener_len(),
|
|
self.end() - kind.closer_len(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl From<&ast::StringLiteral> for StringPart {
|
|
fn from(value: &ast::StringLiteral) -> Self {
|
|
Self {
|
|
range: value.range,
|
|
kind: value.flags.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&ast::BytesLiteral> for StringPart {
|
|
fn from(value: &ast::BytesLiteral) -> Self {
|
|
Self {
|
|
range: value.range,
|
|
kind: value.flags.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&ast::FString> for StringPart {
|
|
fn from(value: &ast::FString) -> Self {
|
|
Self {
|
|
range: value.range,
|
|
kind: value.flags.into(),
|
|
}
|
|
}
|
|
}
|