From f1d367655b80f403454a1c6e90ffaf21ccb95c24 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Tue, 11 Jul 2023 16:40:28 +0200 Subject: [PATCH] Format `target: annotation = value?` expressions (#5661) --- .../skip_magic_trailing_comma.options.json | 3 + .../ruff/expression/annotated_assign.py | 13 + crates/ruff_python_formatter/src/builders.rs | 8 +- .../src/expression/expr_subscript.rs | 29 +- .../src/expression/expr_tuple.rs | 40 +- .../src/expression/mod.rs | 41 +-- .../src/expression/parentheses.rs | 51 +++ .../src/expression/string.rs | 4 +- crates/ruff_python_formatter/src/lib.rs | 8 +- .../src/statement/stmt_ann_assign.rs | 24 +- .../src/statement/stmt_assign.rs | 42 +-- .../src/statement/stmt_delete.rs | 4 +- .../src/statement/stmt_function_def.rs | 8 +- .../src/statement/stmt_import_from.rs | 4 +- .../src/statement/stmt_with.rs | 4 +- .../ruff_python_formatter/tests/fixtures.rs | 10 +- ...ility@miscellaneous__debug_visitor.py.snap | 18 +- ...atibility@miscellaneous__force_pyi.py.snap | 27 +- ...aneous__long_strings_flag_disabled.py.snap | 35 +- ...ty@py_310__pattern_matching_extras.py.snap | 27 +- ...lack_compatibility@py_38__python38.py.snap | 16 +- ...atibility@simple_cases__expression.py.snap | 78 ++-- ...mpatibility@simple_cases__fmtonoff.py.snap | 40 +- ...ple_cases__function_trailing_comma.py.snap | 348 ------------------ ...imple_cases__one_element_subscript.py.snap | 113 ------ ..._cases__return_annotation_brackets.py.snap | 47 +-- ...e_cases__skip_magic_trailing_comma.py.snap | 227 ------------ ...ormat@expression__annotated_assign.py.snap | 37 ++ 28 files changed, 318 insertions(+), 988 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.options.json create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/annotated_assign.py delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function_trailing_comma.py.snap delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__one_element_subscript.py.snap delete mode 100644 crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__skip_magic_trailing_comma.py.snap create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__annotated_assign.py.snap diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.options.json b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.options.json new file mode 100644 index 0000000000..e01e786cb6 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.options.json @@ -0,0 +1,3 @@ +{ + "magic_trailing_comma": "ignore" +} \ No newline at end of file diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/annotated_assign.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/annotated_assign.py new file mode 100644 index 0000000000..0809374ffc --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/annotated_assign.py @@ -0,0 +1,13 @@ +a: string + +b: string = "test" + +b: list[ + string, + int +] = [1, 2] + +b: list[ + string, + int, +] = [1, 2] diff --git a/crates/ruff_python_formatter/src/builders.rs b/crates/ruff_python_formatter/src/builders.rs index 8eeb07a819..368c651c6d 100644 --- a/crates/ruff_python_formatter/src/builders.rs +++ b/crates/ruff_python_formatter/src/builders.rs @@ -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>, { - 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> for OptionalParentheses<'_, 'ast> { +impl<'ast> Format> for ParenthesizeIfExpands<'_, 'ast> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { let saved_level = f.context().node_level(); diff --git a/crates/ruff_python_formatter/src/expression/expr_subscript.rs b/crates/ruff_python_formatter/src/expression/expr_subscript.rs index 7ac3073adc..2a5e90c444 100644 --- a/crates/ruff_python_formatter/src/expression/expr_subscript.rs +++ b/crates/ruff_python_formatter/src/expression/expr_subscript.rs @@ -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 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("]") ])] ) diff --git a/crates/ruff_python_formatter/src/expression/expr_tuple.rs b/crates/ruff_python_formatter/src/expression/expr_tuple.rs index 81107f8422..78ad8279d6 100644 --- a/crates/ruff_python_formatter/src/expression/expr_tuple.rs +++ b/crates/ruff_python_formatter/src/expression/expr_tuple.rs @@ -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` 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 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>, -) -> 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; }; diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 87ba364b56..97c43cf75f 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -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> 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 => { diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index 132e1cf942..2d387d5f46 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -178,6 +178,57 @@ impl<'ast> Format> 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>, +{ + OptionalParentheses { + content: Argument::new(content), + } +} + +pub(crate) struct OptionalParentheses<'content, 'ast> { + content: Argument<'content, PyFormatContext<'ast>>, +} + +impl<'ast> Format> for OptionalParentheses<'_, 'ast> { + fn fmt(&self, f: &mut Formatter>) -> 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>( diff --git a/crates/ruff_python_formatter/src/expression/string.rs b/crates/ruff_python_formatter/src/expression/string.rs index 7b8369d088..34ffebae7d 100644 --- a/crates/ruff_python_formatter/src/expression/string.rs +++ b/crates/ruff_python_formatter/src/expression/string.rs @@ -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> 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) } diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index eebcf05664..79bc92d667 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -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(); diff --git a/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs index 645808d940..c9d5cf2fdf 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs @@ -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 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(()) } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_assign.rs b/crates/ruff_python_formatter/src/statement/stmt_assign.rs index eaff2ef1a3..e8f1175a3e 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_assign.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_assign.rs @@ -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 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], -} - -impl<'a> LhsAssignList<'a> { - const fn new(lhs_assign_list: &'a [Expr]) -> Self { - Self { lhs_assign_list } - } -} - -impl Format> for LhsAssignList<'_> { - fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { - for element in self.lhs_assign_list { - write!(f, [&element.format(), space(), text("="), space(),])?; + for target in targets { + write!(f, [target.format(), space(), text("="), space()])?; } - Ok(()) + + write!(f, [value.format().with_options(Parenthesize::IfBreaks)]) } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_delete.rs b/crates/ruff_python_formatter/src/statement/stmt_delete.rs index e53ff784c7..c3ba7814e8 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_delete.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_delete.rs @@ -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 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) } } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_function_def.rs b/crates/ruff_python_formatter/src/statement/stmt_function_def.rs index d88263e894..a0079b4975 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_function_def.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_function_def.rs @@ -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, PyFormatContext<'_>> for FormatAnyFun space(), text("->"), space(), - return_annotation - .format() - .with_options(Parenthesize::IfBreaks) + optional_parentheses( + &return_annotation.format().with_options(Parenthesize::Never) + ) ] )?; } diff --git a/crates/ruff_python_formatter/src/statement/stmt_import_from.rs b/crates/ruff_python_formatter/src/statement/stmt_import_from.rs index ef8cc13584..353f920e2b 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_import_from.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_import_from.rs @@ -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 for FormatStmtImportFrom { .entries(names.iter().map(|name| (name, name.format()))) .finish() }); - optional_parentheses(&names).fmt(f) + parenthesize_if_expands(&names).fmt(f) } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_with.rs b/crates/ruff_python_formatter/src/statement/stmt_with.rs index c4adb3de4d..03932ff2c1 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_with.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_with.rs @@ -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> 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()) diff --git a/crates/ruff_python_formatter/tests/fixtures.rs b/crates/ruff_python_formatter/tests/fixtures.rs index 7860cd3aae..5d9be66a41 100644 --- a/crates/ruff_python_formatter/tests/fixtures.rs +++ b/crates/ruff_python_formatter/tests/fixtures.rs @@ -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}", diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap index eb2adafb48..784fe8d950 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__debug_visitor.py.snap @@ -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))) ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__force_pyi.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__force_pyi.py.snap index f8f00344e5..b2ea142296 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__force_pyi.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__force_pyi.py.snap @@ -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]: diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap index b4d5258ad2..908e9c1cc9 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@miscellaneous__long_strings_flag_disabled.py.snap @@ -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 \\\\" diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap index 8752abc340..23dd576815 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_310__pattern_matching_extras.py.snap @@ -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 diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap index d706374274..f92c9acaf0 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@py_38__python38.py.snap @@ -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 diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap index 97dc2ee72d..4f4798dc42 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__expression.py.snap @@ -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], - ) + 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] diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap index 7ed99b9322..218da54c3e 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__fmtonoff.py.snap @@ -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, -+ ) - ] - ++ "some big and", ++ "complex subscript", + # 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, - ) + "some big and", + "complex subscript", + # fmt: on + goes + here, + andhere, ] diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function_trailing_comma.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function_trailing_comma.py.snap deleted file mode 100644 index 388d0c3d4e..0000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__function_trailing_comma.py.snap +++ /dev/null @@ -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, -) -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__one_element_subscript.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__one_element_subscript.py.snap deleted file mode 100644 index 1db4d103ba..0000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__one_element_subscript.py.snap +++ /dev/null @@ -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,], -] -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__return_annotation_brackets.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__return_annotation_brackets.py.snap index 664ee153eb..c35111a8b1 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__return_annotation_brackets.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__return_annotation_brackets.py.snap @@ -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 ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__skip_magic_trailing_comma.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__skip_magic_trailing_comma.py.snap deleted file mode 100644 index d87ef4d8a0..0000000000 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__skip_magic_trailing_comma.py.snap +++ /dev/null @@ -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) -``` - - diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__annotated_assign.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__annotated_assign.py.snap new file mode 100644 index 0000000000..55c646d9fd --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__annotated_assign.py.snap @@ -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] +``` + + +