Format target: annotation = value? expressions (#5661)

This commit is contained in:
Micha Reiser 2023-07-11 16:40:28 +02:00 committed by GitHub
parent 0c8ec80d7b
commit f1d367655b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 318 additions and 988 deletions

View file

@ -0,0 +1,3 @@
{
"magic_trailing_comma": "ignore"
}

View file

@ -0,0 +1,13 @@
a: string
b: string = "test"
b: list[
string,
int
] = [1, 2]
b: list[
string,
int,
] = [1, 2]

View file

@ -6,20 +6,20 @@ use ruff_text_size::TextSize;
use rustpython_parser::ast::Ranged;
/// Adds parentheses and indents `content` if it doesn't fit on a line.
pub(crate) fn optional_parentheses<'ast, T>(content: &T) -> OptionalParentheses<'_, 'ast>
pub(crate) fn parenthesize_if_expands<'ast, T>(content: &T) -> ParenthesizeIfExpands<'_, 'ast>
where
T: Format<PyFormatContext<'ast>>,
{
OptionalParentheses {
ParenthesizeIfExpands {
inner: Argument::new(content),
}
}
pub(crate) struct OptionalParentheses<'a, 'ast> {
pub(crate) struct ParenthesizeIfExpands<'a, 'ast> {
inner: Argument<'a, PyFormatContext<'ast>>,
}
impl<'ast> Format<PyFormatContext<'ast>> for OptionalParentheses<'_, 'ast> {
impl<'ast> Format<PyFormatContext<'ast>> for ParenthesizeIfExpands<'_, 'ast> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
let saved_level = f.context().node_level();

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::ExprSubscript;
use rustpython_parser::ast::{Expr, ExprSubscript};
use ruff_formatter::{format_args, write};
use ruff_python_ast::node::AstNode;
@ -6,8 +6,10 @@ use ruff_python_ast::node::AstNode;
use crate::comments::trailing_comments;
use crate::context::NodeLevel;
use crate::context::PyFormatContext;
use crate::expression::expr_tuple::TupleParentheses;
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
default_expression_needs_parentheses, in_parentheses_only_group, NeedsParentheses, Parentheses,
Parenthesize,
};
use crate::prelude::*;
use crate::FormatNodeRule;
@ -42,12 +44,31 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
value.format().fmt(f)?;
}
let format_slice = format_with(|f: &mut PyFormatter| {
let saved_level = f.context().node_level();
f.context_mut()
.set_node_level(NodeLevel::ParenthesizedExpression);
let result = if let Expr::Tuple(tuple) = slice.as_ref() {
tuple
.format()
.with_options(TupleParentheses::Subscript)
.fmt(f)
} else {
slice.format().fmt(f)
};
f.context_mut().set_node_level(saved_level);
result
});
write!(
f,
[group(&format_args![
[in_parentheses_only_group(&format_args![
text("["),
trailing_comments(dangling_comments),
soft_block_indent(&slice.format()),
soft_block_indent(&format_slice),
text("]")
])]
)

View file

@ -1,4 +1,4 @@
use crate::builders::optional_parentheses;
use crate::builders::parenthesize_if_expands;
use crate::comments::{dangling_comments, CommentLinePosition};
use crate::expression::parentheses::{
default_expression_needs_parentheses, parenthesized, NeedsParentheses, Parentheses,
@ -17,6 +17,11 @@ pub enum TupleParentheses {
Default,
/// Effectively `Some(Parentheses)` in `Option<Parentheses>`
Expr(Parentheses),
/// Black omits parentheses for tuples inside of subscripts except if the tuple is parenthesized
/// in the source code.
Subscript,
/// Handle the special case where we remove parentheses even if they were initially present
///
/// Normally, black keeps parentheses, but in the case of loops it formats
@ -86,21 +91,32 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
])]
)
}
[single] => {
// A single element tuple always needs parentheses and a trailing comma
parenthesized("(", &format_args![single.format(), &text(",")], ")").fmt(f)
[single] => match self.parentheses {
TupleParentheses::Subscript
if !is_parenthesized(*range, elts, f.context().source()) =>
{
write!(f, [single.format(), text(",")])
}
_ =>
// A single element tuple always needs parentheses and a trailing comma, except when inside of a subscript
{
parenthesized("(", &format_args![single.format(), text(",")], ")").fmt(f)
}
},
// If the tuple has parentheses, we generally want to keep them. The exception are for
// loops, see `TupleParentheses::StripInsideForLoop` doc comment.
//
// Unlike other expression parentheses, tuple parentheses are part of the range of the
// tuple itself.
elts if is_parenthesized(*range, elts, f)
elts if is_parenthesized(*range, elts, f.context().source())
&& self.parentheses != TupleParentheses::StripInsideForLoop =>
{
parenthesized("(", &ExprSequence::new(elts), ")").fmt(f)
}
elts => optional_parentheses(&ExprSequence::new(elts)).fmt(f),
elts => match self.parentheses {
TupleParentheses::Subscript => group(&ExprSequence::new(elts)).fmt(f),
_ => parenthesize_if_expands(&ExprSequence::new(elts)).fmt(f),
},
}
}
@ -141,15 +157,9 @@ impl NeedsParentheses for ExprTuple {
}
/// Check if a tuple has already had parentheses in the input
fn is_parenthesized(
tuple_range: TextRange,
elts: &[Expr],
f: &mut Formatter<PyFormatContext<'_>>,
) -> bool {
fn is_parenthesized(tuple_range: TextRange, elts: &[Expr], source: &str) -> bool {
let parentheses = '(';
let first_char = &f.context().source()[usize::from(tuple_range.start())..]
.chars()
.next();
let first_char = &source[usize::from(tuple_range.start())..].chars().next();
let Some(first_char) = first_char else {
return false;
};

View file

@ -2,17 +2,16 @@ use rustpython_parser::ast;
use rustpython_parser::ast::{Expr, Operator};
use std::cmp::Ordering;
use crate::builders::optional_parentheses;
use ruff_formatter::{
format_args, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions,
};
use crate::builders::parenthesize_if_expands;
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::visitor::preorder::{walk_expr, PreorderVisitor};
use crate::context::NodeLevel;
use crate::expression::expr_tuple::TupleParentheses;
use crate::expression::parentheses::{
is_expression_parenthesized, parenthesized, NeedsParentheses, Parentheses, Parenthesize,
is_expression_parenthesized, optional_parentheses, parenthesized, NeedsParentheses,
Parentheses, Parenthesize,
};
use crate::expression::string::StringLayout;
use crate::prelude::*;
@ -106,37 +105,9 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
// Add optional parentheses. Ignore if the item renders parentheses itself.
Parentheses::Optional => {
if can_omit_optional_parentheses(item, f.context()) {
let saved_level = f.context().node_level();
// The group id is used as a condition in [`in_parentheses_only`] to create a conditional group
// that is only active if the optional parentheses group expands.
let parens_id = f.group_id("optional_parentheses");
f.context_mut()
.set_node_level(NodeLevel::Expression(Some(parens_id)));
// We can't use `soft_block_indent` here because that would always increment the indent,
// even if the group does not break (the indent is not soft). This would result in
// too deep indentations if a `parenthesized` group expands. Using `indent_if_group_breaks`
// gives us the desired *soft* indentation that is only present if the optional parentheses
// are shown.
let result = group(&format_args![
if_group_breaks(&text("(")),
indent_if_group_breaks(
&format_args![soft_line_break(), format_expr],
parens_id
),
soft_line_break(),
if_group_breaks(&text(")"))
])
.with_group_id(Some(parens_id))
.fmt(f);
f.context_mut().set_node_level(saved_level);
result
} else {
optional_parentheses(&format_expr).fmt(f)
} else {
parenthesize_if_expands(&format_expr).fmt(f)
}
}
Parentheses::Custom | Parentheses::Never => {

View file

@ -178,6 +178,57 @@ impl<'ast> Format<PyFormatContext<'ast>> for FormatParenthesized<'_, 'ast> {
}
}
/// Wraps an expression in parentheses only if it still does not fit after expanding all expressions that start or end with
/// a parentheses (`()`, `[]`, `{}`).
pub(crate) fn optional_parentheses<'content, 'ast, Content>(
content: &'content Content,
) -> OptionalParentheses<'content, 'ast>
where
Content: Format<PyFormatContext<'ast>>,
{
OptionalParentheses {
content: Argument::new(content),
}
}
pub(crate) struct OptionalParentheses<'content, 'ast> {
content: Argument<'content, PyFormatContext<'ast>>,
}
impl<'ast> Format<PyFormatContext<'ast>> for OptionalParentheses<'_, 'ast> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'ast>>) -> FormatResult<()> {
let saved_level = f.context().node_level();
// The group id is used as a condition in [`in_parentheses_only`] to create a conditional group
// that is only active if the optional parentheses group expands.
let parens_id = f.group_id("optional_parentheses");
f.context_mut()
.set_node_level(NodeLevel::Expression(Some(parens_id)));
// We can't use `soft_block_indent` here because that would always increment the indent,
// even if the group does not break (the indent is not soft). This would result in
// too deep indentations if a `parenthesized` group expands. Using `indent_if_group_breaks`
// gives us the desired *soft* indentation that is only present if the optional parentheses
// are shown.
let result = group(&format_args![
if_group_breaks(&text("(")),
indent_if_group_breaks(
&format_args![soft_line_break(), Arguments::from(&self.content)],
parens_id
),
soft_line_break(),
if_group_breaks(&text(")"))
])
.with_group_id(Some(parens_id))
.fmt(f);
f.context_mut().set_node_level(saved_level);
result
}
}
/// Makes `content` a group, but only if the outer expression is parenthesized (a list, parenthesized expression, dict, ...)
/// or if the expression gets parenthesized because it expands over multiple lines.
pub(crate) fn in_parentheses_only_group<'content, 'ast, Content>(

View file

@ -1,4 +1,4 @@
use crate::builders::optional_parentheses;
use crate::builders::parenthesize_if_expands;
use crate::comments::{leading_comments, trailing_comments};
use crate::expression::parentheses::Parentheses;
use crate::prelude::*;
@ -48,7 +48,7 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
let format_continuation = FormatStringContinuation::new(self.constant, self.layout);
if let StringLayout::Default(Some(Parentheses::Custom)) = self.layout {
optional_parentheses(&format_continuation).fmt(f)
parenthesize_if_expands(&format_continuation).fmt(f)
} else {
format_continuation.fmt(f)
}

View file

@ -280,11 +280,9 @@ if True:
#[test]
fn quick_test() {
let src = r#"
if [
aaaaaa,
BBBB,ccccccccc,ddddddd,eeeeeeeeee,ffffff
] & bbbbbbbbbbbbbbbbbbddddddddddddddddddddddddddddbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:
...
def foo() -> tuple[int, int, int,]:
return 2
"#;
// Tokenize once
let mut tokens = Vec::new();

View file

@ -1,5 +1,6 @@
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use crate::prelude::*;
use crate::FormatNodeRule;
use ruff_formatter::write;
use rustpython_parser::ast::StmtAnnAssign;
#[derive(Default)]
@ -7,6 +8,23 @@ pub struct FormatStmtAnnAssign;
impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
fn fmt_fields(&self, item: &StmtAnnAssign, f: &mut PyFormatter) -> FormatResult<()> {
write!(f, [not_yet_implemented(item)])
let StmtAnnAssign {
range: _,
target,
annotation,
value,
simple: _,
} = item;
write!(
f,
[target.format(), text(":"), space(), annotation.format()]
)?;
if let Some(value) = value {
write!(f, [space(), text("="), space(), value.format()])?;
}
Ok(())
}
}

View file

@ -1,12 +1,11 @@
use crate::context::PyFormatContext;
use crate::expression::parentheses::Parenthesize;
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::formatter::Formatter;
use ruff_formatter::prelude::{space, text};
use ruff_formatter::{write, Buffer, Format, FormatResult};
use rustpython_parser::ast::Expr;
use rustpython_parser::ast::StmtAssign;
use ruff_formatter::write;
use crate::expression::parentheses::Parenthesize;
use crate::prelude::*;
use crate::FormatNodeRule;
// Note: This currently does wrap but not the black way so the types below likely need to be
// replaced entirely
//
@ -22,32 +21,11 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
value,
type_comment: _,
} = item;
write!(
f,
[
LhsAssignList::new(targets),
value.format().with_options(Parenthesize::IfBreaks)
]
)
}
}
#[derive(Debug)]
struct LhsAssignList<'a> {
lhs_assign_list: &'a [Expr],
}
for target in targets {
write!(f, [target.format(), space(), text("="), space()])?;
}
impl<'a> LhsAssignList<'a> {
const fn new(lhs_assign_list: &'a [Expr]) -> Self {
Self { lhs_assign_list }
}
}
impl Format<PyFormatContext<'_>> for LhsAssignList<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
for element in self.lhs_assign_list {
write!(f, [&element.format(), space(), text("="), space(),])?;
}
Ok(())
write!(f, [value.format().with_options(Parenthesize::IfBreaks)])
}
}

View file

@ -1,4 +1,4 @@
use crate::builders::{optional_parentheses, PyFormatterExtensions};
use crate::builders::{parenthesize_if_expands, PyFormatterExtensions};
use crate::comments::dangling_node_comments;
use crate::expression::parentheses::Parenthesize;
use crate::{AsFormat, FormatNodeRule, PyFormatter};
@ -36,7 +36,7 @@ impl FormatNodeRule<StmtDelete> for FormatStmtDelete {
}
targets => {
let item = format_with(|f| f.join_comma_separated().nodes(targets.iter()).finish());
optional_parentheses(&item).fmt(f)
parenthesize_if_expands(&item).fmt(f)
}
}
}

View file

@ -1,6 +1,6 @@
use crate::comments::{leading_comments, trailing_comments};
use crate::context::NodeLevel;
use crate::expression::parentheses::Parenthesize;
use crate::expression::parentheses::{optional_parentheses, Parenthesize};
use crate::prelude::*;
use crate::trivia::{lines_after, skip_trailing_trivia};
use crate::FormatNodeRule;
@ -97,9 +97,9 @@ impl FormatRule<AnyFunctionDefinition<'_>, PyFormatContext<'_>> for FormatAnyFun
space(),
text("->"),
space(),
return_annotation
.format()
.with_options(Parenthesize::IfBreaks)
optional_parentheses(
&return_annotation.format().with_options(Parenthesize::Never)
)
]
)?;
}

View file

@ -1,4 +1,4 @@
use crate::builders::{optional_parentheses, PyFormatterExtensions};
use crate::builders::{parenthesize_if_expands, PyFormatterExtensions};
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::prelude::{dynamic_text, format_with, space, text};
use ruff_formatter::{write, Buffer, Format, FormatResult};
@ -43,6 +43,6 @@ impl FormatNodeRule<StmtImportFrom> for FormatStmtImportFrom {
.entries(names.iter().map(|name| (name, name.format())))
.finish()
});
optional_parentheses(&names).fmt(f)
parenthesize_if_expands(&names).fmt(f)
}
}

View file

@ -3,7 +3,7 @@ use ruff_python_ast::node::AnyNodeRef;
use ruff_text_size::TextRange;
use rustpython_parser::ast::{Ranged, StmtAsyncWith, StmtWith, Suite, WithItem};
use crate::builders::optional_parentheses;
use crate::builders::parenthesize_if_expands;
use crate::comments::trailing_comments;
use crate::prelude::*;
use crate::FormatNodeRule;
@ -80,7 +80,7 @@ impl Format<PyFormatContext<'_>> for AnyStatementWith<'_> {
[
text("with"),
space(),
group(&optional_parentheses(&joined_items)),
group(&parenthesize_if_expands(&joined_items)),
text(":"),
trailing_comments(dangling_comments),
block_indent(&self.body().format())

View file

@ -11,7 +11,15 @@ fn black_compatibility() {
let test_file = |input_path: &Path| {
let content = fs::read_to_string(input_path).unwrap();
let options = PyFormatOptions::default();
let options_path = input_path.with_extension("options.json");
let options: PyFormatOptions = if let Ok(options_file) = fs::File::open(options_path) {
let reader = BufReader::new(options_file);
serde_json::from_reader(reader).expect("Options to be a valid Json file")
} else {
PyFormatOptions::default()
};
let printed = format_module(&content, options.clone()).unwrap_or_else(|err| {
panic!(
"Formatting of {} to succeed but encountered error {err}",

View file

@ -44,11 +44,8 @@ class DebugVisitor(Visitor[T]):
```diff
--- Black
+++ Ruff
@@ -1,26 +1,26 @@
@dataclass
class DebugVisitor(Visitor[T]):
- tree_depth: int = 0
+ NOT_YET_IMPLEMENTED_StmtAnnAssign
@@ -3,24 +3,24 @@
tree_depth: int = 0
def visit_default(self, node: LN) -> Iterator[T]:
- indent = ' ' * (2 * self.tree_depth)
@ -79,13 +76,6 @@ class DebugVisitor(Visitor[T]):
@classmethod
def show(cls, code: str) -> None:
@@ -28,5 +28,5 @@
Convenience method for debugging.
"""
- v: DebugVisitor[None] = DebugVisitor()
+ NOT_YET_IMPLEMENTED_StmtAnnAssign
list(v.visit(lib2to3_parse(code)))
```
## Ruff Output
@ -93,7 +83,7 @@ class DebugVisitor(Visitor[T]):
```py
@dataclass
class DebugVisitor(Visitor[T]):
NOT_YET_IMPLEMENTED_StmtAnnAssign
tree_depth: int = 0
def visit_default(self, node: LN) -> Iterator[T]:
indent = " " * (2 * self.tree_depth)
@ -121,7 +111,7 @@ class DebugVisitor(Visitor[T]):
Convenience method for debugging.
"""
NOT_YET_IMPLEMENTED_StmtAnnAssign
v: DebugVisitor[None] = DebugVisitor()
list(v.visit(lib2to3_parse(code)))
```

View file

@ -67,13 +67,13 @@ def eggs() -> Union[str, int]: ...
- def BMethod(self, arg: List[str]) -> None: ...
+ def BMethod(self, arg: List[str]) -> None:
+ ...
+
+
+class C:
+ ...
-class C: ...
+class C:
+ ...
+
+
@hmm
-class D: ...
+class D:
@ -89,29 +89,28 @@ def eggs() -> Union[str, int]: ...
-def foo() -> None: ...
+def foo() -> None:
+ ...
+
+
-class F(A, C): ...
-def spam() -> None: ...
+class F(A, C):
+ ...
+
+
+def spam() -> None:
+ ...
-class F(A, C): ...
-def spam() -> None: ...
+
+
@overload
-def spam(arg: str) -> str: ...
+def spam(arg: str) -> str:
+ ...
+
+
+NOT_YET_IMPLEMENTED_StmtAnnAssign
-var: int = 1
var: int = 1
-def eggs() -> Union[str, int]: ...
+
+def eggs() -> Union[str, int]:
+ ...
```
@ -172,7 +171,7 @@ def spam(arg: str) -> str:
...
NOT_YET_IMPLEMENTED_StmtAnnAssign
var: int = 1
def eggs() -> Union[str, int]:

View file

@ -417,7 +417,7 @@ long_unmergable_string_with_pragma = (
x,
y,
z,
@@ -243,21 +225,13 @@
@@ -243,7 +225,7 @@
func_with_bad_parens(
x,
y,
@ -426,24 +426,7 @@ long_unmergable_string_with_pragma = (
z,
)
-annotated_variable: Final = (
- "This is a large "
- + STRING
- + " that has been "
- + CONCATENATED
- + "using the '+' operator."
-)
-annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
-annotated_variable: Literal[
- "fakse_literal"
-] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\"
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\"
@@ -271,10 +245,10 @@
@@ -271,10 +253,10 @@
def foo():
@ -692,9 +675,17 @@ func_with_bad_parens(
z,
)
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
annotated_variable: Final = (
"This is a large "
+ STRING
+ " that has been "
+ CONCATENATED
+ "using the '+' operator."
)
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal[
"fakse_literal"
] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\"
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\"

View file

@ -147,8 +147,7 @@ match bar1:
match = 1
-case: int = re.match(something)
+NOT_YET_IMPLEMENTED_StmtAnnAssign
case: int = re.match(something)
-match re.match(case):
- case type("match", match):
@ -214,21 +213,19 @@ match bar1:
- case case:
- pass
+NOT_YET_IMPLEMENTED_StmtMatch
-
-
-match match:
- case case:
- pass
-
+NOT_YET_IMPLEMENTED_StmtMatch
-match a, *b(), c:
- case d, *f, g:
- pass
+NOT_YET_IMPLEMENTED_StmtMatch
-
-match something:
- case {
- "key": key as key_1,
@ -237,28 +234,30 @@ match bar1:
- pass
- case {"maybe": something(complicated as this) as that}:
- pass
-
+NOT_YET_IMPLEMENTED_StmtMatch
-match something:
- case 1 as a:
- pass
+NOT_YET_IMPLEMENTED_StmtMatch
- case 2 as b, 3 as c:
- pass
+NOT_YET_IMPLEMENTED_StmtMatch
- case 4 as d, (5 as e), (6 | 7 as g), *h:
- pass
+NOT_YET_IMPLEMENTED_StmtMatch
-
-match bar1:
- case Foo(aa=Callable() as aa, bb=int()):
- print(bar1.aa, bar1.bb)
- case _:
- print("no match", "\n")
-
-
+NOT_YET_IMPLEMENTED_StmtMatch
-match bar1:
- case Foo(
- normal=x, perhaps=[list, {"x": d, "y": 1.0}] as y, otherwise=something, q=t as u
@ -276,7 +275,7 @@ NOT_YET_IMPLEMENTED_StmtMatch
match = 1
NOT_YET_IMPLEMENTED_StmtAnnAssign
case: int = re.match(something)
NOT_YET_IMPLEMENTED_StmtMatch

View file

@ -31,7 +31,7 @@ def t():
```diff
--- Black
+++ Ruff
@@ -8,14 +8,14 @@
@@ -8,7 +8,7 @@
def starred_yield():
my_list = ["value2", "value3"]
@ -40,16 +40,12 @@ def t():
# all right hand side expressions allowed in regular assignments are now also allowed in
# annotated assignments
-a: Tuple[str, int] = "1", 2
-a: Tuple[int, ...] = b, *c, d
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
@@ -18,4 +18,4 @@
def t():
- a: str = yield "a"
+ NOT_YET_IMPLEMENTED_StmtAnnAssign
+ a: str = NOT_YET_IMPLEMENTED_ExprYield
```
## Ruff Output
@ -70,12 +66,12 @@ def starred_yield():
# all right hand side expressions allowed in regular assignments are now also allowed in
# annotated assignments
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
a: Tuple[str, int] = "1", 2
a: Tuple[int, ...] = b, *c, d
def t():
NOT_YET_IMPLEMENTED_StmtAnnAssign
a: str = NOT_YET_IMPLEMENTED_ExprYield
```
## Black Output

View file

@ -357,38 +357,7 @@ last_call()
) # note: no trailing comma pre-3.6
call(*gidgets[:2])
call(a, *gidgets[:2])
@@ -131,34 +131,28 @@
tuple[str, ...]
tuple[str, int, float, dict[str, int]]
tuple[
- str,
- int,
- float,
- dict[str, int],
+ (
+ str,
+ int,
+ float,
+ dict[str, int],
+ )
]
-very_long_variable_name_filters: t.List[
- t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
-]
-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
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
-)
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
- sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
-) # type: ignore
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+NOT_YET_IMPLEMENTED_StmtAnnAssign # type: ignore
slice[0]
@@ -152,13 +152,13 @@
slice[0:1]
slice[0:1:2]
slice[:]
@ -405,7 +374,7 @@ last_call()
numpy[0, :]
numpy[:, i]
numpy[0, :2]
@@ -172,7 +166,7 @@
@@ -172,7 +172,7 @@
numpy[1 : c + 1, c]
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
@ -414,7 +383,7 @@ last_call()
numpy[np.newaxis, :]
(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
{"2.7": dead, "3.7": long_live or die_hard}
@@ -181,10 +175,10 @@
@@ -181,10 +181,10 @@
(SomeName)
SomeName
(Good, Bad, Ugly)
@ -429,11 +398,10 @@ last_call()
(*starred,)
{
"id": "1",
@@ -207,25 +201,15 @@
)
@@ -208,24 +208,14 @@
what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
vars_to_remove
-)
)
-result = (
- session.query(models.Customer.id)
- .filter(
@ -441,7 +409,7 @@ last_call()
- )
- .order_by(models.Customer.id.asc())
- .all()
)
-)
-result = (
- session.query(models.Customer.id)
- .filter(
@ -463,7 +431,7 @@ last_call()
Ø = set()
authors.łukasz.say_thanks()
mapping = {
@@ -237,10 +221,10 @@
@@ -237,10 +227,10 @@
def gen():
@ -478,7 +446,7 @@ last_call()
async def f():
@@ -248,18 +232,22 @@
@@ -248,18 +238,22 @@
print(*[] or [1])
@ -509,7 +477,7 @@ last_call()
...
for i in call():
...
@@ -328,13 +316,18 @@
@@ -328,13 +322,18 @@
):
return True
if (
@ -531,7 +499,7 @@ last_call()
^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
):
return True
@@ -342,7 +335,8 @@
@@ -342,7 +341,8 @@
~aaaaaaaaaaaaaaaa.a
+ aaaaaaaaaaaaaaaa.b
- aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
@ -679,17 +647,23 @@ dict[str, int]
tuple[str, ...]
tuple[str, int, float, dict[str, int]]
tuple[
(
str,
int,
float,
dict[str, int],
)
]
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign
NOT_YET_IMPLEMENTED_StmtAnnAssign # type: ignore
very_long_variable_name_filters: t.List[
t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
]
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
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
)
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
) # type: ignore
slice[0]
slice[0:1]
slice[0:1:2]

View file

@ -276,7 +276,7 @@ d={'a':1,
def spaces_types(
@@ -63,55 +76,54 @@
@@ -63,15 +76,15 @@
something = {
# fmt: off
@ -290,18 +290,12 @@ d={'a':1,
# fmt: off
- 'some big and',
- 'complex subscript',
- # fmt: on
- goes + here,
- andhere,
+ (
+ "some big and",
+ "complex subscript",
+ # fmt: on
+ goes + here,
+ andhere,
+ )
]
# fmt: on
goes + here,
andhere,
@@ -80,38 +93,35 @@
def import_as_names():
# fmt: off
@ -351,7 +345,7 @@ d={'a':1,
# fmt: on
@@ -132,10 +144,10 @@
@@ -132,10 +142,10 @@
"""Another known limitation."""
# fmt: on
# fmt: off
@ -366,7 +360,7 @@ d={'a':1,
# fmt: on
# fmt: off
# ...but comments still get reformatted even though they should not be
@@ -153,9 +165,7 @@
@@ -153,9 +163,7 @@
)
)
# fmt: off
@ -377,7 +371,7 @@ d={'a':1,
# fmt: on
_type_comment_re = re.compile(
r"""
@@ -178,7 +188,7 @@
@@ -178,7 +186,7 @@
$
""",
# fmt: off
@ -386,7 +380,7 @@ d={'a':1,
# fmt: on
)
@@ -216,8 +226,7 @@
@@ -216,8 +224,7 @@
xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5,
)
# fmt: off
@ -488,13 +482,11 @@ something = {
def subscriptlist():
atom[
# fmt: off
(
"some big and",
"complex subscript",
# fmt: on
goes + here,
andhere,
)
]

View file

@ -1,348 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function_trailing_comma.py
---
## Input
```py
def f(a,):
d = {'key': 'value',}
tup = (1,)
def f2(a,b,):
d = {'key': 'value', 'key2': 'value2',}
tup = (1,2,)
def f(a:int=1,):
call(arg={'explode': 'this',})
call2(arg=[1,2,3],)
x = {
"a": 1,
"b": 2,
}["a"]
if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]:
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
]:
json = {"k": {"k2": {"k3": [1,]}}}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(very_long_parameter_so_yeah: str, another_long_parameter: int) -> (
another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not
):
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(this_shouldn_t_get_a_trailing_comma_too)
):
pass
def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
))
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1, (one, two,), argument4, argument5, argument6
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -52,9 +52,9 @@
pass
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
-):
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]:
json = {
"k": {
"k2": {
@@ -80,18 +80,14 @@
pass
-def func() -> (
- also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
- this_shouldn_t_get_a_trailing_comma_too
- )
+def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
+ this_shouldn_t_get_a_trailing_comma_too
):
pass
-def func() -> (
- also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
- this_shouldn_t_get_a_trailing_comma_too
- )
+def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
+ this_shouldn_t_get_a_trailing_comma_too
):
pass
```
## Ruff Output
```py
def f(
a,
):
d = {
"key": "value",
}
tup = (1,)
def f2(
a,
b,
):
d = {
"key": "value",
"key2": "value2",
}
tup = (
1,
2,
)
def f(
a: int = 1,
):
call(
arg={
"explode": "this",
}
)
call2(
arg=[1, 2, 3],
)
x = {
"a": 1,
"b": 2,
}["a"]
if (
a
== {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
):
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
]:
json = {
"k": {
"k2": {
"k3": [
1,
]
}
}
}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(
very_long_parameter_so_yeah: str, another_long_parameter: int
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
pass
def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
):
pass
def func() -> also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```
## Black Output
```py
def f(
a,
):
d = {
"key": "value",
}
tup = (1,)
def f2(
a,
b,
):
d = {
"key": "value",
"key2": "value2",
}
tup = (
1,
2,
)
def f(
a: int = 1,
):
call(
arg={
"explode": "this",
}
)
call2(
arg=[1, 2, 3],
)
x = {
"a": 1,
"b": 2,
}["a"]
if (
a
== {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
):
pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
):
json = {
"k": {
"k2": {
"k3": [
1,
]
}
}
}
# The type annotation shouldn't get a trailing comma since that would change its type.
# Relevant bug report: https://github.com/psf/black/issues/2381.
def some_function_with_a_really_long_name() -> (
returning_a_deeply_nested_import_of_a_type_i_suppose
):
pass
def some_method_with_a_really_long_name(
very_long_parameter_so_yeah: str, another_long_parameter: int
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
def func() -> (
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
this_shouldn_t_get_a_trailing_comma_too
)
):
pass
# Make sure inner one-element tuple won't explode
some_module.some_function(
argument1, (one_element_tuple,), argument4, argument5, argument6
)
# Inner trailing comma causes outer to explode
some_module.some_function(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```

View file

@ -1,113 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py
---
## Input
```py
# We should not treat the trailing comma
# in a single-element subscript.
a: tuple[int,]
b = tuple[int,]
# The magic comma still applies to multi-element subscripts.
c: tuple[int, int,]
d = tuple[int, int,]
# Magic commas still work as expected for non-subscripts.
small_list = [1,]
list_of_types = [tuple[int,],]
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,16 +1,15 @@
# We should not treat the trailing comma
# in a single-element subscript.
-a: tuple[int,]
-b = tuple[int,]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+b = tuple[(int,)]
# The magic comma still applies to multi-element subscripts.
-c: tuple[
- int,
- int,
-]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
d = tuple[
- int,
- int,
+ (
+ int,
+ int,
+ )
]
# Magic commas still work as expected for non-subscripts.
@@ -18,5 +17,5 @@
1,
]
list_of_types = [
- tuple[int,],
+ tuple[(int,)],
]
```
## Ruff Output
```py
# We should not treat the trailing comma
# in a single-element subscript.
NOT_YET_IMPLEMENTED_StmtAnnAssign
b = tuple[(int,)]
# The magic comma still applies to multi-element subscripts.
NOT_YET_IMPLEMENTED_StmtAnnAssign
d = tuple[
(
int,
int,
)
]
# Magic commas still work as expected for non-subscripts.
small_list = [
1,
]
list_of_types = [
tuple[(int,)],
]
```
## Black Output
```py
# We should not treat the trailing comma
# in a single-element subscript.
a: tuple[int,]
b = tuple[int,]
# The magic comma still applies to multi-element subscripts.
c: tuple[
int,
int,
]
d = tuple[
int,
int,
]
# Magic commas still work as expected for non-subscripts.
small_list = [
1,
]
list_of_types = [
tuple[int,],
]
```

View file

@ -122,37 +122,6 @@ def foo() -> tuple[int, int, int,]:
return 2
@@ -99,22 +103,22 @@
return 2
-def foo() -> (
- tuple[
+def foo() -> tuple[
+ (
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
- ]
-):
+ )
+]:
return 2
# Magic trailing comma example
-def foo() -> (
- tuple[
+def foo() -> tuple[
+ (
int,
int,
int,
- ]
-):
+ )
+]:
return 2
```
## Ruff Output
@ -263,24 +232,24 @@ def foo() -> tuple[int, int, int]:
return 2
def foo() -> tuple[
(
def foo() -> (
tuple[
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
)
]:
]
):
return 2
# Magic trailing comma example
def foo() -> tuple[
(
def foo() -> (
tuple[
int,
int,
int,
)
]:
]
):
return 2
```

View file

@ -1,227 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py
---
## Input
```py
# We should not remove the trailing comma in a single-element subscript.
a: tuple[int,]
b = tuple[int,]
# But commas in multiple element subscripts should be removed.
c: tuple[int, int,]
d = tuple[int, int,]
# Remove commas for non-subscripts.
small_list = [1,]
list_of_types = [tuple[int,],]
small_set = {1,}
set_of_types = {tuple[int,],}
# Except single element tuples
small_tuple = (1,)
# Trailing commas in multiple chained non-nested parens.
zero(
one,
).two(
three,
).four(
five,
)
func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5)
(
a,
b,
c,
d,
) = func1(
arg1
) and func2(arg2)
func(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,25 +1,58 @@
# We should not remove the trailing comma in a single-element subscript.
-a: tuple[int,]
-b = tuple[int,]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+b = tuple[(int,)]
# But commas in multiple element subscripts should be removed.
-c: tuple[int, int]
-d = tuple[int, int]
+NOT_YET_IMPLEMENTED_StmtAnnAssign
+d = tuple[
+ (
+ int,
+ int,
+ )
+]
# Remove commas for non-subscripts.
-small_list = [1]
-list_of_types = [tuple[int,]]
+small_list = [
+ 1,
+]
+list_of_types = [
+ tuple[(int,)],
+]
small_set = {1}
-set_of_types = {tuple[int,]}
+set_of_types = {tuple[(int,)]}
# Except single element tuples
small_tuple = (1,)
# Trailing commas in multiple chained non-nested parens.
-zero(one).two(three).four(five)
+zero(
+ one,
+).two(
+ three,
+).four(
+ five,
+)
-func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5)
+func1(arg1).func2(
+ arg2,
+).func3(arg3).func4(
+ arg4,
+).func5(arg5)
-(a, b, c, d) = func1(arg1) and func2(arg2)
+(
+ a,
+ b,
+ c,
+ d,
+) = func1(arg1) and func2(arg2)
-func(argument1, (one, two), argument4, argument5, argument6)
+func(
+ argument1,
+ (
+ one,
+ two,
+ ),
+ argument4,
+ argument5,
+ argument6,
+)
```
## Ruff Output
```py
# We should not remove the trailing comma in a single-element subscript.
NOT_YET_IMPLEMENTED_StmtAnnAssign
b = tuple[(int,)]
# But commas in multiple element subscripts should be removed.
NOT_YET_IMPLEMENTED_StmtAnnAssign
d = tuple[
(
int,
int,
)
]
# Remove commas for non-subscripts.
small_list = [
1,
]
list_of_types = [
tuple[(int,)],
]
small_set = {1}
set_of_types = {tuple[(int,)]}
# Except single element tuples
small_tuple = (1,)
# Trailing commas in multiple chained non-nested parens.
zero(
one,
).two(
three,
).four(
five,
)
func1(arg1).func2(
arg2,
).func3(arg3).func4(
arg4,
).func5(arg5)
(
a,
b,
c,
d,
) = func1(arg1) and func2(arg2)
func(
argument1,
(
one,
two,
),
argument4,
argument5,
argument6,
)
```
## Black Output
```py
# We should not remove the trailing comma in a single-element subscript.
a: tuple[int,]
b = tuple[int,]
# But commas in multiple element subscripts should be removed.
c: tuple[int, int]
d = tuple[int, int]
# Remove commas for non-subscripts.
small_list = [1]
list_of_types = [tuple[int,]]
small_set = {1}
set_of_types = {tuple[int,]}
# Except single element tuples
small_tuple = (1,)
# Trailing commas in multiple chained non-nested parens.
zero(one).two(three).four(five)
func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5)
(a, b, c, d) = func1(arg1) and func2(arg2)
func(argument1, (one, two), argument4, argument5, argument6)
```

View file

@ -0,0 +1,37 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/annotated_assign.py
---
## Input
```py
a: string
b: string = "test"
b: list[
string,
int
] = [1, 2]
b: list[
string,
int,
] = [1, 2]
```
## Output
```py
a: string
b: string = "test"
b: list[string, int] = [1, 2]
b: list[
string,
int,
] = [1, 2]
```