mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:28 +00:00
Use reserved width to include line suffix measurement (#6901)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
edfd888bd6
commit
a3f4d7745a
16 changed files with 504 additions and 80 deletions
|
@ -14,6 +14,6 @@ def function(a:int=42):
|
||||||
a
|
a
|
||||||
b
|
b
|
||||||
"""
|
"""
|
||||||
# There's a NBSP + 3 spaces before
|
# There's a NBSP + 3 spaces before
|
||||||
# And 4 spaces on the next line
|
# And 4 spaces on the next line
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -66,3 +66,6 @@ g2 = ( # a
|
||||||
h1 = ((((1, 2))))
|
h1 = ((((1, 2))))
|
||||||
h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"))))
|
h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"))))
|
||||||
h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"
|
h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"
|
||||||
|
|
||||||
|
i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break
|
||||||
|
|
||||||
|
|
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py
vendored
Normal file
6
crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma.
|
||||||
|
# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129
|
||||||
|
i2 = "" # type: Add space before leading NBSP followed by spaces
|
||||||
|
i3 = "" #type: A space is added
|
||||||
|
i4 = "" # type: Add space before leading NBSP followed by a space
|
||||||
|
i5 = "" # type: Add space before leading NBSP
|
|
@ -1,6 +1,9 @@
|
||||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
use std::borrow::Cow;
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use ruff_formatter::{format_args, write, FormatError, SourceCode};
|
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||||
|
|
||||||
|
use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
|
||||||
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
||||||
|
|
||||||
|
@ -151,19 +154,22 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
|
||||||
empty_lines(lines_before_comment),
|
empty_lines(lines_before_comment),
|
||||||
format_comment(trailing)
|
format_comment(trailing)
|
||||||
],
|
],
|
||||||
|
// Reserving width isn't necessary because we don't split
|
||||||
|
// comments and the empty lines expand any enclosing group.
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
expand_parent()
|
expand_parent()
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
write!(
|
// A trailing comment at the end of a line has a reserved width to
|
||||||
f,
|
// consider during line measurement.
|
||||||
[
|
// ```python
|
||||||
line_suffix(&format_args![space(), space(), format_comment(trailing)], 0),
|
// tup = (
|
||||||
expand_parent()
|
// "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
]
|
// ) # Some comment
|
||||||
)?;
|
// ```
|
||||||
|
trailing_end_of_line_comment(trailing).fmt(f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
trailing.mark_formatted();
|
trailing.mark_formatted();
|
||||||
|
@ -262,13 +268,7 @@ impl Format<PyFormatContext<'_>> for FormatDanglingOpenParenthesisComments<'_> {
|
||||||
"Expected dangling comment to be at the end of the line"
|
"Expected dangling comment to be at the end of the line"
|
||||||
);
|
);
|
||||||
|
|
||||||
write!(
|
trailing_end_of_line_comment(comment).fmt(f)?;
|
||||||
f,
|
|
||||||
[
|
|
||||||
line_suffix(&format_args!(space(), space(), format_comment(comment)), 0),
|
|
||||||
expand_parent()
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
comment.mark_formatted();
|
comment.mark_formatted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,50 +291,11 @@ pub(crate) struct FormatComment<'a> {
|
||||||
impl Format<PyFormatContext<'_>> for FormatComment<'_> {
|
impl Format<PyFormatContext<'_>> for FormatComment<'_> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let slice = self.comment.slice();
|
let slice = self.comment.slice();
|
||||||
let comment_text = slice.text(SourceCode::new(f.context().source()));
|
let source = SourceCode::new(f.context().source());
|
||||||
|
|
||||||
let trimmed = comment_text.trim_end();
|
let normalized_comment = normalize_comment(self.comment, source)?;
|
||||||
let trailing_whitespace_len = comment_text.text_len() - trimmed.text_len();
|
|
||||||
|
|
||||||
let Some(content) = trimmed.strip_prefix('#') else {
|
format_normalized_comment(normalized_comment, slice.range()).fmt(f)
|
||||||
return Err(FormatError::syntax_error(
|
|
||||||
"Didn't find expected comment token `#`",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fast path for correctly formatted comments:
|
|
||||||
// * Start with a `#` and are followed by a space
|
|
||||||
// * Have no trailing whitespace.
|
|
||||||
if trailing_whitespace_len == TextSize::new(0) && content.starts_with(' ') {
|
|
||||||
return source_text_slice(slice.range(), ContainsNewlines::No).fmt(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, [source_position(slice.start()), text("#")])?;
|
|
||||||
|
|
||||||
// Starts with a non breaking space
|
|
||||||
let start_offset =
|
|
||||||
if content.starts_with('\u{A0}') && !content.trim_start().starts_with("type:") {
|
|
||||||
// Replace non-breaking space with a space (if not followed by a normal space)
|
|
||||||
"#\u{A0}".text_len()
|
|
||||||
} else {
|
|
||||||
'#'.text_len()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add a space between the `#` and the text if the source contains none.
|
|
||||||
if !content.is_empty() && !content.starts_with([' ', '!', ':', '#', '\'']) {
|
|
||||||
write!(f, [space()])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = slice.start() + start_offset;
|
|
||||||
let end = slice.end() - trailing_whitespace_len;
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[
|
|
||||||
source_text_slice(TextRange::new(start, end), ContainsNewlines::No),
|
|
||||||
source_position(slice.end())
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,3 +333,145 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLines {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper that constructs a formattable element using a reserved-width line-suffix
|
||||||
|
/// for normalized comments.
|
||||||
|
///
|
||||||
|
/// * Black normalization of `SourceComment`.
|
||||||
|
/// * Line suffix with reserved width for the final, normalized content.
|
||||||
|
/// * Expands parent node.
|
||||||
|
pub(crate) const fn trailing_end_of_line_comment(
|
||||||
|
comment: &SourceComment,
|
||||||
|
) -> FormatTrailingEndOfLineComment {
|
||||||
|
FormatTrailingEndOfLineComment { comment }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FormatTrailingEndOfLineComment<'a> {
|
||||||
|
comment: &'a SourceComment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format<PyFormatContext<'_>> for FormatTrailingEndOfLineComment<'_> {
|
||||||
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
let slice = self.comment.slice();
|
||||||
|
let source = SourceCode::new(f.context().source());
|
||||||
|
|
||||||
|
let normalized_comment = normalize_comment(self.comment, source)?;
|
||||||
|
|
||||||
|
// Start with 2 because of the two leading spaces.
|
||||||
|
let mut reserved_width = 2;
|
||||||
|
|
||||||
|
// SAFE: The formatted file is <= 4GB, and each comment should as well.
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
for c in normalized_comment.chars() {
|
||||||
|
reserved_width += match c {
|
||||||
|
'\t' => f.options().tab_width().value(),
|
||||||
|
c => c.width().unwrap_or(0) as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
line_suffix(
|
||||||
|
&format_args![
|
||||||
|
space(),
|
||||||
|
space(),
|
||||||
|
format_normalized_comment(normalized_comment, slice.range())
|
||||||
|
],
|
||||||
|
reserved_width
|
||||||
|
),
|
||||||
|
expand_parent()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper that constructs formattable normalized comment text as efficiently as
|
||||||
|
/// possible.
|
||||||
|
///
|
||||||
|
/// * If the content is unaltered then format with source text slice strategy and no
|
||||||
|
/// unnecessary allocations.
|
||||||
|
/// * If the content is modified then make as few allocations as possible and use
|
||||||
|
/// a dynamic text element at the original slice's start position.
|
||||||
|
pub(crate) const fn format_normalized_comment(
|
||||||
|
comment: Cow<'_, str>,
|
||||||
|
range: TextRange,
|
||||||
|
) -> FormatNormalizedComment<'_> {
|
||||||
|
FormatNormalizedComment { comment, range }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FormatNormalizedComment<'a> {
|
||||||
|
comment: Cow<'a, str>,
|
||||||
|
range: TextRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format<PyFormatContext<'_>> for FormatNormalizedComment<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||||
|
match self.comment {
|
||||||
|
Cow::Borrowed(borrowed) => source_text_slice(
|
||||||
|
TextRange::at(self.range.start(), borrowed.text_len()),
|
||||||
|
ContainsNewlines::No,
|
||||||
|
)
|
||||||
|
.fmt(f),
|
||||||
|
|
||||||
|
Cow::Owned(ref owned) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
dynamic_text(owned, Some(self.range.start())),
|
||||||
|
source_position(self.range.end())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper for normalizing comments efficiently.
|
||||||
|
///
|
||||||
|
/// * Return as fast as possible without making unnecessary allocations.
|
||||||
|
/// * Trim any trailing whitespace.
|
||||||
|
/// * Normalize for a leading '# '.
|
||||||
|
/// * Retain non-breaking spaces for 'type:' pragmas by leading with '# \u{A0}'.
|
||||||
|
fn normalize_comment<'a>(
|
||||||
|
comment: &'a SourceComment,
|
||||||
|
source: SourceCode<'a>,
|
||||||
|
) -> FormatResult<Cow<'a, str>> {
|
||||||
|
let slice = comment.slice();
|
||||||
|
let comment_text = slice.text(source);
|
||||||
|
|
||||||
|
let trimmed = comment_text.trim_end();
|
||||||
|
|
||||||
|
let Some(content) = trimmed.strip_prefix('#') else {
|
||||||
|
return Err(FormatError::syntax_error(
|
||||||
|
"Didn't find expected comment token `#`",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
if content.is_empty() {
|
||||||
|
return Ok(Cow::Borrowed("#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for correctly formatted comments:
|
||||||
|
// * Start with a `# '.
|
||||||
|
// * Have no trailing whitespace.
|
||||||
|
if content.starts_with([' ', '!', ':', '#', '\'']) {
|
||||||
|
return Ok(Cow::Borrowed(trimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if content.starts_with('\u{A0}') {
|
||||||
|
let trimmed = content.trim_start_matches('\u{A0}');
|
||||||
|
|
||||||
|
// Black adds a space before the non-breaking space if part of a type pragma.
|
||||||
|
if trimmed.trim_start().starts_with("type:") {
|
||||||
|
return Ok(Cow::Owned(std::format!("# \u{A0}{trimmed}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black replaces the non-breaking space with a space if followed by a space.
|
||||||
|
if trimmed.starts_with(' ') {
|
||||||
|
return Ok(Cow::Owned(std::format!("# {trimmed}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Cow::Owned(std::format!("# {}", content.trim_start())))
|
||||||
|
}
|
||||||
|
|
|
@ -247,9 +247,12 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
||||||
if format_expression.inspect(f)?.will_break() {
|
if format_expression.inspect(f)?.will_break() {
|
||||||
// The group here is necessary because `format_expression` may contain IR elements
|
// The group here is necessary because `format_expression` may contain IR elements
|
||||||
// that refer to the group id
|
// that refer to the group id
|
||||||
group(&format_expression)
|
group(&format_args![
|
||||||
|
text("("),
|
||||||
|
soft_block_indent(&format_expression),
|
||||||
|
text(")")
|
||||||
|
])
|
||||||
.with_group_id(Some(group_id))
|
.with_group_id(Some(group_id))
|
||||||
.should_expand(true)
|
|
||||||
.fmt(f)
|
.fmt(f)
|
||||||
} else {
|
} else {
|
||||||
// Only add parentheses if it makes the expression fit on the line.
|
// Only add parentheses if it makes the expression fit on the line.
|
||||||
|
|
|
@ -156,7 +156,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,11 +112,18 @@
|
@@ -108,11 +112,20 @@
|
||||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -176,7 +176,10 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite
|
||||||
+ ], # type: ignore
|
+ ], # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type]
|
-aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type]
|
||||||
|
+aaaaaaaaaaaaa, bbbbbbbbb = map(
|
||||||
|
+ list, map(itertools.chain.from_iterable, zip(*items))
|
||||||
|
+) # type: ignore[arg-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ruff Output
|
## Ruff Output
|
||||||
|
@ -310,7 +313,9 @@ call_to_some_function_asdf(
|
||||||
], # type: ignore
|
], # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type]
|
aaaaaaaaaaaaa, bbbbbbbbb = map(
|
||||||
|
list, map(itertools.chain.from_iterable, zip(*items))
|
||||||
|
) # type: ignore[arg-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Black Output
|
## Black Output
|
||||||
|
|
|
@ -300,7 +300,18 @@ last_call()
|
||||||
) # note: no trailing comma pre-3.6
|
) # note: no trailing comma pre-3.6
|
||||||
call(*gidgets[:2])
|
call(*gidgets[:2])
|
||||||
call(a, *gidgets[:2])
|
call(a, *gidgets[:2])
|
||||||
@@ -328,13 +329,18 @@
|
@@ -142,7 +143,9 @@
|
||||||
|
xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
||||||
|
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||||
|
)
|
||||||
|
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
||||||
|
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
|
||||||
|
+ ..., List[SomeClass]
|
||||||
|
+] = classmethod( # type: ignore
|
||||||
|
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||||
|
)
|
||||||
|
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
|
||||||
|
@@ -328,13 +331,18 @@
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
if (
|
if (
|
||||||
|
@ -322,7 +333,7 @@ last_call()
|
||||||
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
|
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
@@ -342,7 +348,8 @@
|
@@ -342,7 +350,8 @@
|
||||||
~aaaaaaaaaaaaaaaa.a
|
~aaaaaaaaaaaaaaaa.a
|
||||||
+ aaaaaaaaaaaaaaaa.b
|
+ aaaaaaaaaaaaaaaa.b
|
||||||
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
|
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
|
||||||
|
@ -482,7 +493,9 @@ very_long_variable_name_filters: t.List[
|
||||||
xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
||||||
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||||
)
|
)
|
||||||
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
|
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
|
||||||
|
..., List[SomeClass]
|
||||||
|
] = classmethod( # type: ignore
|
||||||
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
|
||||||
)
|
)
|
||||||
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
|
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/power_op_spacing.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```py
|
||||||
|
def function(**kwargs):
|
||||||
|
t = a**2 + b**3
|
||||||
|
return t ** 2
|
||||||
|
|
||||||
|
|
||||||
|
def function_replace_spaces(**kwargs):
|
||||||
|
t = a **2 + b** 3 + c ** 4
|
||||||
|
|
||||||
|
|
||||||
|
def function_dont_replace_spaces():
|
||||||
|
{**a, **b, **c}
|
||||||
|
|
||||||
|
|
||||||
|
a = 5**~4
|
||||||
|
b = 5 ** f()
|
||||||
|
c = -(5**2)
|
||||||
|
d = 5 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5
|
||||||
|
j = super().name ** 5
|
||||||
|
k = [(2**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2**63], [1, 2**63])]
|
||||||
|
n = count <= 10**5
|
||||||
|
o = settings(max_examples=10**6)
|
||||||
|
p = {(k, k**2): v**2 for k, v in pairs}
|
||||||
|
q = [10**i for i in range(6)]
|
||||||
|
r = x**y
|
||||||
|
|
||||||
|
a = 5.0**~4.0
|
||||||
|
b = 5.0 ** f()
|
||||||
|
c = -(5.0**2.0)
|
||||||
|
d = 5.0 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5.0
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5.0 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5.0
|
||||||
|
j = super().name ** 5.0
|
||||||
|
k = [(2.0**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2.0**63.0], [1.0, 2**63.0])]
|
||||||
|
n = count <= 10**5.0
|
||||||
|
o = settings(max_examples=10**6.0)
|
||||||
|
p = {(k, k**2): v**2.0 for k, v in pairs}
|
||||||
|
q = [10.5**i for i in range(6)]
|
||||||
|
|
||||||
|
|
||||||
|
# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873)
|
||||||
|
if hasattr(view, "sum_of_weights"):
|
||||||
|
return np.divide( # type: ignore[no-any-return]
|
||||||
|
view.variance, # type: ignore[union-attr]
|
||||||
|
view.sum_of_weights, # type: ignore[union-attr]
|
||||||
|
out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr]
|
||||||
|
where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
|
||||||
|
return np.divide(
|
||||||
|
where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Black Differences
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- Black
|
||||||
|
+++ Ruff
|
||||||
|
@@ -55,9 +55,11 @@
|
||||||
|
view.variance, # type: ignore[union-attr]
|
||||||
|
view.sum_of_weights, # type: ignore[union-attr]
|
||||||
|
out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr]
|
||||||
|
- where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr]
|
||||||
|
+ where=view.sum_of_weights**2
|
||||||
|
+ > view.sum_of_weights_squared, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
|
||||||
|
return np.divide(
|
||||||
|
- where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore
|
||||||
|
+ where=view.sum_of_weights_of_weight_long**2
|
||||||
|
+ > view.sum_of_weights_squared, # type: ignore
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruff Output
|
||||||
|
|
||||||
|
```py
|
||||||
|
def function(**kwargs):
|
||||||
|
t = a**2 + b**3
|
||||||
|
return t**2
|
||||||
|
|
||||||
|
|
||||||
|
def function_replace_spaces(**kwargs):
|
||||||
|
t = a**2 + b**3 + c**4
|
||||||
|
|
||||||
|
|
||||||
|
def function_dont_replace_spaces():
|
||||||
|
{**a, **b, **c}
|
||||||
|
|
||||||
|
|
||||||
|
a = 5**~4
|
||||||
|
b = 5 ** f()
|
||||||
|
c = -(5**2)
|
||||||
|
d = 5 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5
|
||||||
|
j = super().name ** 5
|
||||||
|
k = [(2**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2**63], [1, 2**63])]
|
||||||
|
n = count <= 10**5
|
||||||
|
o = settings(max_examples=10**6)
|
||||||
|
p = {(k, k**2): v**2 for k, v in pairs}
|
||||||
|
q = [10**i for i in range(6)]
|
||||||
|
r = x**y
|
||||||
|
|
||||||
|
a = 5.0**~4.0
|
||||||
|
b = 5.0 ** f()
|
||||||
|
c = -(5.0**2.0)
|
||||||
|
d = 5.0 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5.0
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5.0 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5.0
|
||||||
|
j = super().name ** 5.0
|
||||||
|
k = [(2.0**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2.0**63.0], [1.0, 2**63.0])]
|
||||||
|
n = count <= 10**5.0
|
||||||
|
o = settings(max_examples=10**6.0)
|
||||||
|
p = {(k, k**2): v**2.0 for k, v in pairs}
|
||||||
|
q = [10.5**i for i in range(6)]
|
||||||
|
|
||||||
|
|
||||||
|
# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873)
|
||||||
|
if hasattr(view, "sum_of_weights"):
|
||||||
|
return np.divide( # type: ignore[no-any-return]
|
||||||
|
view.variance, # type: ignore[union-attr]
|
||||||
|
view.sum_of_weights, # type: ignore[union-attr]
|
||||||
|
out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr]
|
||||||
|
where=view.sum_of_weights**2
|
||||||
|
> view.sum_of_weights_squared, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
|
||||||
|
return np.divide(
|
||||||
|
where=view.sum_of_weights_of_weight_long**2
|
||||||
|
> view.sum_of_weights_squared, # type: ignore
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Black Output
|
||||||
|
|
||||||
|
```py
|
||||||
|
def function(**kwargs):
|
||||||
|
t = a**2 + b**3
|
||||||
|
return t**2
|
||||||
|
|
||||||
|
|
||||||
|
def function_replace_spaces(**kwargs):
|
||||||
|
t = a**2 + b**3 + c**4
|
||||||
|
|
||||||
|
|
||||||
|
def function_dont_replace_spaces():
|
||||||
|
{**a, **b, **c}
|
||||||
|
|
||||||
|
|
||||||
|
a = 5**~4
|
||||||
|
b = 5 ** f()
|
||||||
|
c = -(5**2)
|
||||||
|
d = 5 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5
|
||||||
|
j = super().name ** 5
|
||||||
|
k = [(2**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2**63], [1, 2**63])]
|
||||||
|
n = count <= 10**5
|
||||||
|
o = settings(max_examples=10**6)
|
||||||
|
p = {(k, k**2): v**2 for k, v in pairs}
|
||||||
|
q = [10**i for i in range(6)]
|
||||||
|
r = x**y
|
||||||
|
|
||||||
|
a = 5.0**~4.0
|
||||||
|
b = 5.0 ** f()
|
||||||
|
c = -(5.0**2.0)
|
||||||
|
d = 5.0 ** f["hi"]
|
||||||
|
e = lazy(lambda **kwargs: 5)
|
||||||
|
f = f() ** 5.0
|
||||||
|
g = a.b**c.d
|
||||||
|
h = 5.0 ** funcs.f()
|
||||||
|
i = funcs.f() ** 5.0
|
||||||
|
j = super().name ** 5.0
|
||||||
|
k = [(2.0**idx, value) for idx, value in pairs]
|
||||||
|
l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
|
||||||
|
m = [([2.0**63.0], [1.0, 2**63.0])]
|
||||||
|
n = count <= 10**5.0
|
||||||
|
o = settings(max_examples=10**6.0)
|
||||||
|
p = {(k, k**2): v**2.0 for k, v in pairs}
|
||||||
|
q = [10.5**i for i in range(6)]
|
||||||
|
|
||||||
|
|
||||||
|
# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873)
|
||||||
|
if hasattr(view, "sum_of_weights"):
|
||||||
|
return np.divide( # type: ignore[no-any-return]
|
||||||
|
view.variance, # type: ignore[union-attr]
|
||||||
|
view.sum_of_weights, # type: ignore[union-attr]
|
||||||
|
out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr]
|
||||||
|
where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
|
||||||
|
return np.divide(
|
||||||
|
where=view.sum_of_weights_of_weight_long**2 > view.sum_of_weights_squared, # type: ignore
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ async def main():
|
||||||
```diff
|
```diff
|
||||||
--- Black
|
--- Black
|
||||||
+++ Ruff
|
+++ Ruff
|
||||||
@@ -21,7 +21,9 @@
|
@@ -21,11 +21,15 @@
|
||||||
|
|
||||||
# Check comments
|
# Check comments
|
||||||
async def main():
|
async def main():
|
||||||
|
@ -103,6 +103,13 @@ async def main():
|
||||||
+ )
|
+ )
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
- await asyncio.sleep(1) # Hello
|
||||||
|
+ await (
|
||||||
|
+ asyncio.sleep(1) # Hello
|
||||||
|
+ )
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -138,7 +145,9 @@ async def main():
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await asyncio.sleep(1) # Hello
|
await (
|
||||||
|
asyncio.sleep(1) # Hello
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|
|
@ -50,14 +50,17 @@ assert (
|
||||||
) #
|
) #
|
||||||
|
|
||||||
assert sort_by_dependency(
|
assert sort_by_dependency(
|
||||||
@@ -25,9 +25,9 @@
|
@@ -25,9 +25,11 @@
|
||||||
class A:
|
class A:
|
||||||
def foo(self):
|
def foo(self):
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
- aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc(
|
- aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc(
|
||||||
+ aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member
|
- xxxxxxxxxxxx
|
||||||
xxxxxxxxxxxx
|
|
||||||
- ) # pylint: disable=no-member
|
- ) # pylint: disable=no-member
|
||||||
|
+ aaaaaaaaaaaaaaaaaaa = (
|
||||||
|
+ bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member
|
||||||
|
+ xxxxxxxxxxxx
|
||||||
|
+ )
|
||||||
+ )
|
+ )
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,9 +97,11 @@ importA
|
||||||
class A:
|
class A:
|
||||||
def foo(self):
|
def foo(self):
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member
|
aaaaaaaaaaaaaaaaaaa = (
|
||||||
|
bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member
|
||||||
xxxxxxxxxxxx
|
xxxxxxxxxxxx
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test(self, othr):
|
def test(self, othr):
|
||||||
|
|
|
@ -72,6 +72,9 @@ g2 = ( # a
|
||||||
h1 = ((((1, 2))))
|
h1 = ((((1, 2))))
|
||||||
h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"))))
|
h2 = ((((1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"))))
|
||||||
h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"
|
h3 = 1, "qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq"
|
||||||
|
|
||||||
|
i1 = ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",) # This should break
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
@ -275,6 +278,10 @@ h3 = (
|
||||||
1,
|
1,
|
||||||
"qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq",
|
"qweiurpoiqwurepqiurpqirpuqoiwrupqoirupqoirupqoiurpqiorupwqiourpqurpqurpqurpqurpqurpqurüqurqpuriq",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
i1 = (
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
) # This should break
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -268,7 +268,9 @@ d13 = (
|
||||||
)
|
)
|
||||||
|
|
||||||
# Doesn't fit, default
|
# Doesn't fit, default
|
||||||
d2 = x.e().esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfsdddd() #
|
d2 = (
|
||||||
|
x.e().esadjkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfsdddd() #
|
||||||
|
)
|
||||||
|
|
||||||
# Doesn't fit, fluent style
|
# Doesn't fit, fluent style
|
||||||
d3 = (
|
d3 = (
|
||||||
|
|
|
@ -204,7 +204,13 @@ del (
|
||||||
|
|
||||||
# NOTE: This shouldn't format. See https://github.com/astral-sh/ruff/issues/5630.
|
# NOTE: This shouldn't format. See https://github.com/astral-sh/ruff/issues/5630.
|
||||||
# Delete something
|
# Delete something
|
||||||
del x, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, b, c, d # Delete these
|
del (
|
||||||
|
x,
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
) # Delete these
|
||||||
# Ready to delete
|
# Ready to delete
|
||||||
|
|
||||||
# Delete something
|
# Delete something
|
||||||
|
|
|
@ -324,7 +324,9 @@ finally:
|
||||||
|
|
||||||
try: # 1 preceding: any, following: first in body, enclosing: try
|
try: # 1 preceding: any, following: first in body, enclosing: try
|
||||||
print(1) # 2 preceding: last in body, following: fist in alt body, enclosing: try
|
print(1) # 2 preceding: last in body, following: fist in alt body, enclosing: try
|
||||||
except ZeroDivisionError: # 3 preceding: test, following: fist in alt body, enclosing: try
|
except (
|
||||||
|
ZeroDivisionError
|
||||||
|
): # 3 preceding: test, following: fist in alt body, enclosing: try
|
||||||
print(2) # 4 preceding: last in body, following: fist in alt body, enclosing: exc
|
print(2) # 4 preceding: last in body, following: fist in alt body, enclosing: exc
|
||||||
except: # 5 preceding: last in body, following: fist in alt body, enclosing: try
|
except: # 5 preceding: last in body, following: fist in alt body, enclosing: try
|
||||||
print(2) # 6 preceding: last in body, following: fist in alt body, enclosing: exc
|
print(2) # 6 preceding: last in body, following: fist in alt body, enclosing: exc
|
||||||
|
|
|
@ -57,7 +57,9 @@ while aVeryLongConditionThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGo
|
||||||
else:
|
else:
|
||||||
...
|
...
|
||||||
|
|
||||||
while some_condition(unformatted, args) and anotherCondition or aThirdCondition: # comment
|
while (
|
||||||
|
some_condition(unformatted, args) and anotherCondition or aThirdCondition
|
||||||
|
): # comment
|
||||||
print("Do something")
|
print("Do something")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/trailing_comments.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```py
|
||||||
|
# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma.
|
||||||
|
# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129
|
||||||
|
i2 = "" # type: Add space before leading NBSP followed by spaces
|
||||||
|
i3 = "" #type: A space is added
|
||||||
|
i4 = "" # type: Add space before leading NBSP followed by a space
|
||||||
|
i5 = "" # type: Add space before leading NBSP
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```py
|
||||||
|
# As of adding this fixture Black adds a space before the non-breaking space if part of a type pragma.
|
||||||
|
# https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/comments.py#L122-L129
|
||||||
|
i2 = "" # type: Add space before leading NBSP followed by spaces
|
||||||
|
i3 = "" # type: A space is added
|
||||||
|
i4 = "" # type: Add space before leading NBSP followed by a space
|
||||||
|
i5 = "" # type: Add space before leading NBSP
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue