mirror of https://github.com/astral-sh/ruff
Add FString support to binary like formatting
## Summary This is the last part of the string - binary like formatting. It adds support for handling fstrings the same as "regular" strings. ## Test Plan I added a test for both binary and comparison. Small improvements across several projects **This PR** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99966 | 2760 | 58 | | **transformers** | 0.99929 | 2587 | 454 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99978 | 3496 | 2173 | | **warehouse** | 0.99825 | 648 | 22 | | **zulip** | 0.99950 | 1437 | 27 | **Base** | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99966 | 2760 | 58 | | transformers | 0.99928 | 2587 | 454 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99978 | 3496 | 2173 | | warehouse | 0.99824 | 648 | 22 | | zulip | 0.99948 | 1437 | 28 | <!-- How was it tested? -->
This commit is contained in:
parent
05951dd338
commit
41f0aad7b3
|
|
@ -84,6 +84,15 @@ self._assert_skipping(
|
||||||
+ x
|
+ x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b + c + d +
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
f"bbbbbb{z}bbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc"
|
||||||
|
% aaaaaaaaaaaa
|
||||||
|
+ x
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
b < c > d <
|
b < c > d <
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,15 @@ assert (
|
||||||
in caplog.messages
|
in caplog.messages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b < c > d <
|
||||||
|
f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc"
|
||||||
|
% aaaaaaaaaaaa
|
||||||
|
> x
|
||||||
|
)
|
||||||
|
|
||||||
c = (a >
|
c = (a >
|
||||||
# test leading binary comment
|
# test leading binary comment
|
||||||
"a" "b" * b
|
"a" "b" * b
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,16 @@ use smallvec::SmallVec;
|
||||||
|
|
||||||
use ruff_formatter::write;
|
use ruff_formatter::write;
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
BytesConstant, Constant, Expr, ExprAttribute, ExprBinOp, ExprCompare, ExprConstant,
|
Constant, Expr, ExprAttribute, ExprBinOp, ExprCompare, ExprConstant, ExprUnaryOp, UnaryOp,
|
||||||
ExprUnaryOp, StringConstant, UnaryOp,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::comments::{leading_comments, trailing_comments, Comments, SourceComment};
|
use crate::comments::{leading_comments, trailing_comments, Comments, SourceComment};
|
||||||
use crate::expression::expr_constant::ExprConstantLayout;
|
|
||||||
use crate::expression::parentheses::{
|
use crate::expression::parentheses::{
|
||||||
in_parentheses_only_group, in_parentheses_only_soft_line_break,
|
in_parentheses_only_group, in_parentheses_only_soft_line_break,
|
||||||
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized,
|
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized,
|
||||||
write_in_parentheses_only_group_end_tag, write_in_parentheses_only_group_start_tag,
|
write_in_parentheses_only_group_end_tag, write_in_parentheses_only_group_start_tag,
|
||||||
};
|
};
|
||||||
use crate::expression::string::StringLayout;
|
use crate::expression::string::{AnyString, FormatString, StringLayout};
|
||||||
use crate::expression::OperatorPrecedence;
|
use crate::expression::OperatorPrecedence;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
|
@ -197,29 +195,12 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
|
||||||
let mut string_operands = flat_binary
|
let mut string_operands = flat_binary
|
||||||
.operands()
|
.operands()
|
||||||
.filter_map(|(index, operand)| {
|
.filter_map(|(index, operand)| {
|
||||||
if let Expr::Constant(
|
AnyString::from_expression(operand.expression())
|
||||||
constant @ ExprConstant {
|
.filter(|string| {
|
||||||
value:
|
string.is_implicit_concatenated()
|
||||||
Constant::Str(StringConstant {
|
&& !is_expression_parenthesized(string.into(), source)
|
||||||
implicit_concatenated: true,
|
})
|
||||||
..
|
.map(|string| (index, string, operand))
|
||||||
})
|
|
||||||
| Constant::Bytes(BytesConstant {
|
|
||||||
implicit_concatenated: true,
|
|
||||||
..
|
|
||||||
}),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) = operand.expression()
|
|
||||||
{
|
|
||||||
if is_expression_parenthesized(constant.into(), source) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((index, constant, operand))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
|
|
@ -296,11 +277,11 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
operand.leading_binary_comments().map(leading_comments),
|
operand.leading_binary_comments().map(leading_comments),
|
||||||
string_constant
|
leading_comments(comments.leading(&string_constant)),
|
||||||
.format()
|
FormatString::new(&string_constant).with_layout(
|
||||||
.with_options(ExprConstantLayout::String(
|
StringLayout::ImplicitConcatenatedStringInBinaryLike,
|
||||||
StringLayout::ImplicitConcatenatedStringInBinaryLike,
|
),
|
||||||
)),
|
trailing_comments(comments.trailing(&string_constant)),
|
||||||
operand.trailing_binary_comments().map(trailing_comments),
|
operand.trailing_binary_comments().map(trailing_comments),
|
||||||
line_suffix_boundary(),
|
line_suffix_boundary(),
|
||||||
]
|
]
|
||||||
|
|
@ -311,12 +292,16 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
|
||||||
// "a" "b" + c
|
// "a" "b" + c
|
||||||
// ^^^^^^^-- format the first operand of a binary expression
|
// ^^^^^^^-- format the first operand of a binary expression
|
||||||
// ```
|
// ```
|
||||||
string_constant
|
write!(
|
||||||
.format()
|
f,
|
||||||
.with_options(ExprConstantLayout::String(
|
[
|
||||||
StringLayout::ImplicitConcatenatedStringInBinaryLike,
|
leading_comments(comments.leading(&string_constant)),
|
||||||
))
|
FormatString::new(&string_constant).with_layout(
|
||||||
.fmt(f)?;
|
StringLayout::ImplicitConcatenatedStringInBinaryLike
|
||||||
|
),
|
||||||
|
trailing_comments(comments.trailing(&string_constant)),
|
||||||
|
]
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the right operator and start the group for the right side (if any)
|
// Write the right operator and start the group for the right side (if any)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use bitflags::bitflags;
|
||||||
|
|
||||||
use ruff_formatter::{format_args, write, FormatError, FormatOptions, TabWidth};
|
use ruff_formatter::{format_args, write, FormatError, FormatOptions, TabWidth};
|
||||||
use ruff_python_ast::node::AnyNodeRef;
|
use ruff_python_ast::node::AnyNodeRef;
|
||||||
use ruff_python_ast::{self as ast, ExprConstant, ExprFString};
|
use ruff_python_ast::{self as ast, Constant, ExprConstant, ExprFString, ExpressionRef};
|
||||||
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
||||||
use ruff_python_parser::{Mode, Tok};
|
use ruff_python_parser::{Mode, Tok};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
|
@ -24,12 +24,26 @@ enum Quoting {
|
||||||
Preserve,
|
Preserve,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub(super) enum AnyString<'a> {
|
pub(super) enum AnyString<'a> {
|
||||||
Constant(&'a ExprConstant),
|
Constant(&'a ExprConstant),
|
||||||
FString(&'a ExprFString),
|
FString(&'a ExprFString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AnyString<'a> {
|
impl<'a> AnyString<'a> {
|
||||||
|
pub(crate) fn from_expression(expression: &'a Expr) -> Option<AnyString<'a>> {
|
||||||
|
match expression {
|
||||||
|
Expr::Constant(
|
||||||
|
constant @ ExprConstant {
|
||||||
|
value: Constant::Str(_) | Constant::Bytes(_),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => Some(AnyString::Constant(constant)),
|
||||||
|
Expr::FString(fstring) => Some(AnyString::FString(fstring)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn quoting(&self, locator: &Locator) -> Quoting {
|
fn quoting(&self, locator: &Locator) -> Quoting {
|
||||||
match self {
|
match self {
|
||||||
Self::Constant(_) => Quoting::CanChange,
|
Self::Constant(_) => Quoting::CanChange,
|
||||||
|
|
@ -50,7 +64,7 @@ impl<'a> AnyString<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the string is implicitly concatenated.
|
/// Returns `true` if the string is implicitly concatenated.
|
||||||
fn implicit_concatenated(&self) -> bool {
|
pub(super) fn is_implicit_concatenated(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Constant(ExprConstant { value, .. }) => value.is_implicit_concatenated(),
|
Self::Constant(ExprConstant { value, .. }) => value.is_implicit_concatenated(),
|
||||||
Self::FString(ExprFString {
|
Self::FString(ExprFString {
|
||||||
|
|
@ -79,6 +93,15 @@ impl<'a> From<&AnyString<'a>> for AnyNodeRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&AnyString<'a>> for ExpressionRef<'a> {
|
||||||
|
fn from(value: &AnyString<'a>) -> Self {
|
||||||
|
match value {
|
||||||
|
AnyString::Constant(expr) => ExpressionRef::Constant(expr),
|
||||||
|
AnyString::FString(expr) => ExpressionRef::FString(expr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct FormatString<'a> {
|
pub(super) struct FormatString<'a> {
|
||||||
string: &'a AnyString<'a>,
|
string: &'a AnyString<'a>,
|
||||||
layout: StringLayout,
|
layout: StringLayout,
|
||||||
|
|
@ -97,7 +120,7 @@ pub enum StringLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FormatString<'a> {
|
impl<'a> FormatString<'a> {
|
||||||
pub(super) fn new(string: &'a AnyString) -> Self {
|
pub(super) fn new(string: &'a AnyString<'a>) -> Self {
|
||||||
if let AnyString::Constant(constant) = string {
|
if let AnyString::Constant(constant) = string {
|
||||||
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
|
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +140,7 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
match self.layout {
|
match self.layout {
|
||||||
StringLayout::Default => {
|
StringLayout::Default => {
|
||||||
if self.string.implicit_concatenated() {
|
if self.string.is_implicit_concatenated() {
|
||||||
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
FormatStringPart::new(
|
FormatStringPart::new(
|
||||||
|
|
@ -972,9 +995,10 @@ fn format_docstring_line(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::expression::string::count_indentation_like_black;
|
|
||||||
use ruff_formatter::TabWidth;
|
use ruff_formatter::TabWidth;
|
||||||
|
|
||||||
|
use crate::expression::string::count_indentation_like_black;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_indentation_like_black() {
|
fn test_indentation_like_black() {
|
||||||
let tab_width = TabWidth::try_from(8).unwrap();
|
let tab_width = TabWidth::try_from(8).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,15 @@ self._assert_skipping(
|
||||||
+ x
|
+ x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b + c + d +
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
f"bbbbbb{z}bbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc"
|
||||||
|
% aaaaaaaaaaaa
|
||||||
|
+ x
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
b < c > d <
|
b < c > d <
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
@ -266,6 +275,12 @@ self._assert_skipping(
|
||||||
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa + x
|
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa + x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b + c + d + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
f"bbbbbb{z}bbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa + x
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
b < c > d < "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
b < c > d < "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,15 @@ assert (
|
||||||
in caplog.messages
|
in caplog.messages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b < c > d <
|
||||||
|
f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc"
|
||||||
|
% aaaaaaaaaaaa
|
||||||
|
> x
|
||||||
|
)
|
||||||
|
|
||||||
c = (a >
|
c = (a >
|
||||||
# test leading binary comment
|
# test leading binary comment
|
||||||
"a" "b" * b
|
"a" "b" * b
|
||||||
|
|
@ -356,6 +365,12 @@ assert (
|
||||||
"be silently ignored by the index" in caplog.messages
|
"be silently ignored by the index" in caplog.messages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
b < c > d < f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
"cccccccccccccccccccccccccc" % aaaaaaaaaaaa > x
|
||||||
|
)
|
||||||
|
|
||||||
c = (
|
c = (
|
||||||
a >
|
a >
|
||||||
# test leading binary comment
|
# test leading binary comment
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue