diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_expression.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_expression.py new file mode 100644 index 00000000000000..21c844293112df --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_expression.py @@ -0,0 +1,27 @@ +(aaaaaaaa + + # trailing operator comment + b # trailing right comment +) + + +(aaaaaaaa # trailing left comment + + # trailing operator comment + # leading right comment + b +) + + +# Black breaks the right side first for the following expressions: +aaaaaaaaaaaaaa + caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal(argument1, argument2, argument3) +aaaaaaaaaaaaaa + [bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee] +aaaaaaaaaaaaaa + (bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee) +aaaaaaaaaaaaaa + { key1:bbbbbbbbbbbbbbbbbbbbbb, key2: ccccccccccccccccccccc, key3: dddddddddddddddd, key4: eeeeeee } +aaaaaaaaaaaaaa + { bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee } +aaaaaaaaaaaaaa + [a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ] +aaaaaaaaaaaaaa + (a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ) +aaaaaaaaaaaaaa + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb} + + +# But only for expressions that have a statement parent. +not (aaaaaaaaaaaaaa + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}) +[a + [bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] in c ] diff --git a/crates/ruff_python_formatter/src/builders.rs b/crates/ruff_python_formatter/src/builders.rs index 45a9ee248ed16e..48d79e441128fc 100644 --- a/crates/ruff_python_formatter/src/builders.rs +++ b/crates/ruff_python_formatter/src/builders.rs @@ -12,7 +12,7 @@ pub(crate) trait PyFormatterExtensions<'ast, 'buf> { /// /// * [`NodeLevel::Module`]: Up to two empty lines /// * [`NodeLevel::CompoundStatement`]: Up to one empty line - /// * [`NodeLevel::Parenthesized`]: No empty lines + /// * [`NodeLevel::Expression`]: No empty lines fn join_nodes<'fmt>(&'fmt mut self, level: NodeLevel) -> JoinNodesBuilder<'fmt, 'ast, 'buf>; } @@ -48,18 +48,18 @@ impl<'fmt, 'ast, 'buf> JoinNodesBuilder<'fmt, 'ast, 'buf> { { let node_level = self.node_level; let separator = format_with(|f: &mut PyFormatter| match node_level { - NodeLevel::TopLevel => match lines_before(f.context().contents(), node.start()) { + NodeLevel::TopLevel => match lines_before(node.start(), f.context().contents()) { 0 | 1 => hard_line_break().fmt(f), 2 => empty_line().fmt(f), _ => write!(f, [empty_line(), empty_line()]), }, NodeLevel::CompoundStatement => { - match lines_before(f.context().contents(), node.start()) { + match lines_before(node.start(), f.context().contents()) { 0 | 1 => hard_line_break().fmt(f), _ => empty_line().fmt(f), } } - NodeLevel::Parenthesized => hard_line_break().fmt(f), + NodeLevel::Expression => hard_line_break().fmt(f), }); self.entry_with_separator(&separator, content); @@ -200,7 +200,7 @@ no_leading_newline = 30"# // Removes all empty lines #[test] fn ranged_builder_parenthesized_level() { - let printed = format_ranged(NodeLevel::Parenthesized); + let printed = format_ranged(NodeLevel::Expression); assert_eq!( &printed, diff --git a/crates/ruff_python_formatter/src/comments/format.rs b/crates/ruff_python_formatter/src/comments/format.rs index 286905ce85e50d..faa0f288287ca2 100644 --- a/crates/ruff_python_formatter/src/comments/format.rs +++ b/crates/ruff_python_formatter/src/comments/format.rs @@ -39,7 +39,7 @@ impl Format> for FormatLeadingComments<'_> { for comment in leading_comments { let slice = comment.slice(); - let lines_after_comment = lines_after(f.context().contents(), slice.end()); + let lines_after_comment = lines_after(slice.end(), f.context().contents()); write!( f, [format_comment(comment), empty_lines(lines_after_comment)] @@ -80,7 +80,7 @@ impl Format> for FormatLeadingAlternateBranchComments<'_> { if let Some(first_leading) = self.comments.first() { // Leading comments only preserves the lines after the comment but not before. // Insert the necessary lines. - if lines_before(f.context().contents(), first_leading.slice().start()) > 1 { + if lines_before(first_leading.slice().start(), f.context().contents()) > 1 { write!(f, [empty_line()])?; } @@ -88,7 +88,7 @@ impl Format> for FormatLeadingAlternateBranchComments<'_> { } else if let Some(last_preceding) = self.last_node { // The leading comments formatting ensures that it preserves the right amount of lines after // We need to take care of this ourselves, if there's no leading `else` comment. - if lines_after(f.context().contents(), last_preceding.end()) > 1 { + if lines_after(last_preceding.end(), f.context().contents()) > 1 { write!(f, [empty_line()])?; } } @@ -132,7 +132,7 @@ impl Format> for FormatTrailingComments<'_> { has_trailing_own_line_comment |= trailing.position().is_own_line(); if has_trailing_own_line_comment { - let lines_before_comment = lines_before(f.context().contents(), slice.start()); + let lines_before_comment = lines_before(slice.start(), f.context().contents()); // A trailing comment at the end of a body or list // ```python @@ -200,7 +200,7 @@ impl Format> for FormatDanglingComments<'_> { f, [ format_comment(comment), - empty_lines(lines_after(f.context().contents(), comment.slice().end())) + empty_lines(lines_after(comment.slice().end(), f.context().contents())) ] )?; @@ -301,7 +301,7 @@ impl Format> for FormatEmptyLines { }, // Remove all whitespace in parenthesized expressions - NodeLevel::Parenthesized => write!(f, [hard_line_break()]), + NodeLevel::Expression => write!(f, [hard_line_break()]), } } } diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 062cd17a8de9b6..c90d446b255637 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -520,8 +520,8 @@ fn handle_trailing_end_of_line_condition_comment<'a>( if preceding.ptr_eq(last_before_colon) { let mut start = preceding.end(); while let Some((offset, c)) = find_first_non_trivia_character_in_range( - locator.contents(), TextRange::new(start, following.start()), + locator.contents(), ) { match c { ':' => { @@ -655,7 +655,7 @@ fn handle_trailing_binary_expression_left_or_operator_comment<'a>( ); let operator_offset = loop { - match find_first_non_trivia_character_in_range(locator.contents(), between_operands_range) { + match find_first_non_trivia_character_in_range(between_operands_range, locator.contents()) { // Skip over closing parens Some((offset, ')')) => { between_operands_range = @@ -733,17 +733,17 @@ fn find_pos_only_slash_offset( locator: &Locator, ) -> Option { // First find the comma separating the two arguments - find_first_non_trivia_character_in_range(locator.contents(), between_arguments_range).and_then( + find_first_non_trivia_character_in_range(between_arguments_range, locator.contents()).and_then( |(comma_offset, comma)| { debug_assert_eq!(comma, ','); // Then find the position of the `/` operator find_first_non_trivia_character_in_range( - locator.contents(), TextRange::new( comma_offset + TextSize::new(1), between_arguments_range.end(), ), + locator.contents(), ) .map(|(offset, c)| { debug_assert_eq!(c, '/'); diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index bef99c451492bf..074d5cc08d7609 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -82,5 +82,5 @@ pub(crate) enum NodeLevel { CompoundStatement, /// Formatting nodes that are enclosed in a parenthesized expression. - Parenthesized, + Expression, } diff --git a/crates/ruff_python_formatter/src/expression/expr_bin_op.rs b/crates/ruff_python_formatter/src/expression/expr_bin_op.rs index 102e55b2a7ccb5..c15194c9a2b886 100644 --- a/crates/ruff_python_formatter/src/expression/expr_bin_op.rs +++ b/crates/ruff_python_formatter/src/expression/expr_bin_op.rs @@ -1,12 +1,119 @@ -use crate::{verbatim_text, FormatNodeRule, PyFormatter}; -use ruff_formatter::{write, Buffer, FormatResult}; -use rustpython_parser::ast::ExprBinOp; +use crate::comments::trailing_comments; +use crate::prelude::*; +use crate::FormatNodeRule; +use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule}; +use ruff_python_ast::node::{AstNode}; +use rustpython_parser::ast::{ + Constant, Expr, ExprAttribute, ExprBinOp, ExprConstant, ExprUnaryOp, Operator, Unaryop, +}; #[derive(Default)] pub struct FormatExprBinOp; impl FormatNodeRule for FormatExprBinOp { fn fmt_fields(&self, item: &ExprBinOp, f: &mut PyFormatter) -> FormatResult<()> { - write!(f, [verbatim_text(item.range)]) + let ExprBinOp { + left, + right, + op, + range: _, + } = item; + + let needs_space = !is_simple_power_expression(item); + + let before_operator_space = if needs_space { + soft_line_break_or_space() + } else { + soft_line_break() + }; + + let comments = f.context().comments().clone(); + let operator_comments = comments.dangling_comments(item.as_any_node_ref()); + + write!( + f, + [ + left.format(), + before_operator_space, + op.format(), + trailing_comments(operator_comments), + ] + )?; + + // Format the operator on its own line if it has any trailing comments at the right side has leading comments. + if !operator_comments.is_empty() && comments.has_leading_comments(right.as_ref().into()) { + write!(f, [hard_line_break()])?; + } else if needs_space { + write!(f, [space()])?; + } + + write!(f, [group(&right.format())]) + } + + fn fmt_dangling_comments(&self, _node: &ExprBinOp, _f: &mut PyFormatter) -> FormatResult<()> { + // Handled inside of `fmt_fields` + Ok(()) + } +} + +const fn is_simple_power_expression(expr: &ExprBinOp) -> bool { + expr.op.is_pow() && is_simple_power_operand(&expr.left) && is_simple_power_operand(&expr.right) +} + +/// Return `true` if an [`Expr`] adheres to Black's definition of a non-complex +/// expression, in the context of a power operation. +const fn is_simple_power_operand(expr: &Expr) -> bool { + match expr { + Expr::UnaryOp(ExprUnaryOp { + op: Unaryop::Not, .. + }) => false, + Expr::Constant(ExprConstant { + value: Constant::Complex { .. } | Constant::Float(_) | Constant::Int(_), + .. + }) => true, + Expr::Name(_) => true, + Expr::UnaryOp(ExprUnaryOp { operand, .. }) => is_simple_power_operand(operand), + Expr::Attribute(ExprAttribute { value, .. }) => is_simple_power_operand(value), + _ => false, + } +} + +#[derive(Copy, Clone)] +pub struct FormatOperator; + +impl<'ast> AsFormat> for Operator { + type Format<'a> = FormatRefWithRule<'a, Operator, FormatOperator, PyFormatContext<'ast>>; + + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, FormatOperator) + } +} + +impl<'ast> IntoFormat> for Operator { + type Format = FormatOwnedWithRule>; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, FormatOperator) + } +} + +impl FormatRule> for FormatOperator { + fn fmt(&self, item: &Operator, f: &mut Formatter>) -> FormatResult<()> { + let operator = match item { + Operator::Add => "+", + Operator::Sub => "-", + Operator::Mult => "*", + Operator::MatMult => "@", + Operator::Div => "/", + Operator::Mod => "%", + Operator::Pow => "**", + Operator::LShift => "<<", + Operator::RShift => ">>", + Operator::BitOr => "|", + Operator::BitXor => "^", + Operator::BitAnd => "&", + Operator::FloorDiv => "//", + }; + + text(operator).fmt(f) } } diff --git a/crates/ruff_python_formatter/src/expression/maybe_parenthesize.rs b/crates/ruff_python_formatter/src/expression/maybe_parenthesize.rs deleted file mode 100644 index aa7b4f5702b8d0..00000000000000 --- a/crates/ruff_python_formatter/src/expression/maybe_parenthesize.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::context::NodeLevel; -use crate::prelude::*; -use ruff_formatter::{format_args, write}; -use rustpython_parser::ast::Expr; - -/// Formats the passed expression. Adds parentheses if the expression doesn't fit on a line. -pub(crate) const fn maybe_parenthesize(expression: &Expr) -> MaybeParenthesize { - MaybeParenthesize { expression } -} - -pub(crate) struct MaybeParenthesize<'a> { - expression: &'a Expr, -} - -impl Format> for MaybeParenthesize<'_> { - fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { - let saved_level = f.context().node_level(); - f.context_mut().set_node_level(NodeLevel::Parenthesized); - - let result = if needs_parentheses(self.expression) { - write!( - f, - [group(&format_args![ - if_group_breaks(&text("(")), - soft_block_indent(&self.expression.format()), - if_group_breaks(&text(")")) - ])] - ) - } else { - // Don't add parentheses around expressions that have parentheses on their own (e.g. list, dict, tuple, call expression) - self.expression.format().fmt(f) - }; - - f.context_mut().set_node_level(saved_level); - - result - } -} - -const fn needs_parentheses(expr: &Expr) -> bool { - !matches!( - expr, - Expr::Tuple(_) - | Expr::List(_) - | Expr::Set(_) - | Expr::Dict(_) - | Expr::ListComp(_) - | Expr::SetComp(_) - | Expr::DictComp(_) - | Expr::GeneratorExp(_) - | Expr::Call(_) - ) -} diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index 039c104d55370e..748fc37d8d833f 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -1,6 +1,13 @@ +use crate::context::NodeLevel; use crate::prelude::*; -use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatRule}; -use rustpython_parser::ast::Expr; +use crate::trivia::{ + find_first_non_trivia_character_after, find_first_non_trivia_character_before, +}; +use ruff_formatter::{ + format_args, write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions, +}; +use rustpython_parser::ast::{Expr, Ranged}; +use std::fmt::Debug; pub(crate) mod expr_attribute; pub(crate) mod expr_await; @@ -29,14 +36,47 @@ pub(crate) mod expr_tuple; pub(crate) mod expr_unary_op; pub(crate) mod expr_yield; pub(crate) mod expr_yield_from; -pub(crate) mod maybe_parenthesize; + +#[derive(Copy, Clone, Debug, Default)] +pub enum Parentheses { + /// Only emits parentheses if the expression is parenthesized in the source code. + #[default] + Preserve, + + /// Adds parentheses if the expression doesn't fit on a line OR if the expression is parenthesized in the source code. + Optional, + + /// Adds parentheses only if the expression doesn't fit on a line. + IfBreaks, +} + +impl Parentheses { + const fn is_if_breaks(self) -> bool { + matches!(self, Parentheses::IfBreaks) + } + + const fn is_preserve(self) -> bool { + matches!(self, Parentheses::Preserve) + } +} #[derive(Default)] -pub struct FormatExpr; +pub struct FormatExpr { + parentheses: Parentheses, +} + +impl FormatRuleWithOptions> for FormatExpr { + type Options = Parentheses; + + fn with_options(mut self, options: Self::Options) -> Self { + self.parentheses = options; + self + } +} impl FormatRule> for FormatExpr { fn fmt(&self, item: &Expr, f: &mut PyFormatter) -> FormatResult<()> { - match item { + let format_expr = format_with(|f| match item { Expr::BoolOp(expr) => expr.format().fmt(f), Expr::NamedExpr(expr) => expr.format().fmt(f), Expr::BinOp(expr) => expr.format().fmt(f), @@ -64,10 +104,75 @@ impl FormatRule> for FormatExpr { Expr::List(expr) => expr.format().fmt(f), Expr::Tuple(expr) => expr.format().fmt(f), Expr::Slice(expr) => expr.format().fmt(f), - } + }); + + let parenthesize = !self.parentheses.is_if_breaks() + && is_expression_parenthesized(item, f.context().contents()); + + let saved_level = f.context().node_level(); + f.context_mut().set_node_level(NodeLevel::Expression); + + let result = if parenthesize { + // Optional or Preserve and expression has parentheses in source code. + write!( + f, + [group(&format_args![ + text("("), + soft_block_indent(&format_expr), + text(")") + ])] + ) + } else if !self.parentheses.is_preserve() && !has_own_parentheses(item) { + // Optional or IfBreaks: Add parentheses if the expression doesn't fit on a line (and doesn't have its own parentheses). + write!( + f, + [group(&format_args![ + if_group_breaks(&text("(")), + soft_block_indent(&format_expr), + if_group_breaks(&text(")")) + ])] + ) + } else { + // Either `Preserve` and expression had no parentheses in the source code OR + // Expression has its own parentheses and doesn't require parenthesizing. + Format::fmt(&format_expr, f) + }; + + f.context_mut().set_node_level(saved_level); + + result } } +fn is_expression_parenthesized(expr: &Expr, contents: &str) -> bool { + // Search backwards to avoid ambiguity with `(a, )` and because it's faster + matches!( + find_first_non_trivia_character_after(expr.end(), contents), + Some((_, ')')) + ) + // Search forwards to confirm that this is not a nested expression `(5 + d * 3)` + && matches!( + find_first_non_trivia_character_before(expr.start(), contents), + Some((_, '(')) + ) +} + +/// Returns `true` if `expr` adds its own parentheses. +pub(crate) const fn has_own_parentheses(expr: &Expr) -> bool { + matches!( + expr, + Expr::Tuple(_) + | Expr::List(_) + | Expr::Set(_) + | Expr::Dict(_) + | Expr::ListComp(_) + | Expr::SetComp(_) + | Expr::DictComp(_) + | Expr::GeneratorExp(_) + | Expr::Call(_) + ) +} + impl<'ast> AsFormat> for Expr { type Format<'a> = FormatRefWithRule<'a, Expr, FormatExpr, PyFormatContext<'ast>>; fn format(&self) -> Self::Format<'_> { diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index dcbf68acc9bc1e..ed80da25330dc0 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -207,8 +207,11 @@ if True: let formatted_code = printed.as_code(); - let reformatted = - format_module(formatted_code).expect("Expected formatted code to be valid syntax"); + let reformatted = format_module(formatted_code).unwrap_or_else(|err| { + panic!( + "Formatted code resulted introduced a syntax error {err:#?}. Code:\n{formatted_code}" + ) + }); if reformatted.as_code() != formatted_code { let diff = TextDiff::from_lines(formatted_code, reformatted.as_code()) diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap index 12a415a0577f49..58017bff256377 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_py.snap @@ -109,7 +109,21 @@ async def wat(): ```diff --- Black +++ Ruff -@@ -19,14 +19,9 @@ +@@ -4,10 +4,12 @@ + # + # Has many lines. Many, many lines. + # Many, many, many lines. +-"""Module docstring. ++( ++ """Module docstring. + + Possibly also many, many lines. + """ ++) + + import os.path + import sys +@@ -19,9 +21,6 @@ import fast except ImportError: import slow as fast @@ -117,16 +131,9 @@ async def wat(): - -# Some comment before a function. y = 1 --( -- # some strings -- y # type: ignore --) -+# some strings -+y # type: ignore - - - def function(default=None): -@@ -93,4 +88,4 @@ + ( + # some strings +@@ -93,4 +92,4 @@ # Some closing comments. # Maybe Vim or Emacs directives for formatting. @@ -144,10 +151,12 @@ async def wat(): # # Has many lines. Many, many lines. # Many, many, many lines. -"""Module docstring. +( + """Module docstring. Possibly also many, many lines. """ +) import os.path import sys @@ -160,8 +169,10 @@ try: except ImportError: import slow as fast y = 1 -# some strings -y # type: ignore +( + # some strings + y # type: ignore +) def function(default=None): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap index e59d18b7729b88..a99f43ca87c2d1 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__expression_py.snap @@ -276,17 +276,7 @@ last_call() Name None True -@@ -22,119 +23,83 @@ - v1 << 2 - 1 >> v2 - 1 % finished --1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 --((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) -+1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 -+((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) - not great - ~great - +value +@@ -30,111 +31,79 @@ -1 ~int and not v1 ^ 123 + v2 | True (~int) and (not ((v1 ^ (123 + v2)) | True)) @@ -312,14 +302,14 @@ last_call() (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) --( + ( - (super_long_variable_name or None) - if (1 if super_long_test_name else 2) - else (str or bytes or None) --) ++ (super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None) + ) -{"2.7": dead, "3.7": (long_live or die_hard)} -{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} -+(super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None) +{'2.7': dead, '3.7': (long_live or die_hard)} +{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} {**a, **b, **c} @@ -417,18 +407,21 @@ last_call() dict[str, int] tuple[str, ...] -tuple[str, int, float, dict[str, int]] - tuple[ +-tuple[ - str, - int, - float, - dict[str, int], ++( ++ tuple[ + str, int, float, dict[str, int] ] ++) +tuple[str, int, float, dict[str, int],] very_long_variable_name_filters: t.List[ t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], ] -@@ -144,9 +109,9 @@ +@@ -144,9 +113,9 @@ xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) @@ -441,7 +434,7 @@ last_call() slice[0] slice[0:1] slice[0:1:2] -@@ -174,57 +139,29 @@ +@@ -174,57 +143,29 @@ numpy[:, ::-1] numpy[np.newaxis, :] (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) @@ -450,9 +443,8 @@ last_call() +{'2.7': dead, '3.7': long_live or die_hard} +{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'} [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] --(SomeName) + (SomeName) SomeName -+SomeName (Good, Bad, Ugly) (i for i in (1, 2, 3)) -((i**2) for i in (1, 2, 3)) @@ -511,7 +503,7 @@ last_call() Ø = set() authors.łukasz.say_thanks() mapping = { -@@ -237,134 +174,86 @@ +@@ -237,114 +178,80 @@ def gen(): yield from outside_of_generator @@ -655,30 +647,7 @@ last_call() + ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n ): return True --( -- aaaaaaaaaaaaaaaa -- + aaaaaaaaaaaaaaaa -- - aaaaaaaaaaaaaaaa -- * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) -- / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) --) -+aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) - aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa --( -- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -- >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -- << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --) -+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - bbbb >> bbbb * bbbb --( -- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -- ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -- ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --) -+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - last_call() - # standalone comment at ENDMARKER + ( ``` ## Ruff Output @@ -709,8 +678,8 @@ Name1 or Name2 and Name3 or Name4 v1 << 2 1 >> v2 1 % finished -1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 -((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) +1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 +((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) not great ~great +value @@ -731,7 +700,9 @@ str or None if True else str or bytes or None (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) -(super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None) +( + (super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None) +) {'2.7': dead, '3.7': (long_live or die_hard)} {'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} {**a, **b, **c} @@ -782,9 +753,11 @@ call.me(maybe) list[str] dict[str, int] tuple[str, ...] -tuple[ +( + tuple[ str, int, float, dict[str, int] ] +) tuple[str, int, float, dict[str, int],] very_long_variable_name_filters: t.List[ t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], @@ -828,7 +801,7 @@ numpy[np.newaxis, :] {'2.7': dead, '3.7': long_live or die_hard} {'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'} [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] -SomeName +(SomeName) SomeName (Good, Bad, Ugly) (i for i in (1, 2, 3)) @@ -936,11 +909,25 @@ if ( ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n ): return True -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +( + aaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaa + * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) + / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +) aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) bbbb >> bbbb * bbbb -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) last_call() # standalone comment at ENDMARKER ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__slices_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__slices_py.snap index 902e4035e2ec4e..fcfa1edd86bd66 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__slices_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__slices_py.snap @@ -76,16 +76,51 @@ x[ ```diff --- Black +++ Ruff -@@ -56,4 +56,8 @@ +@@ -31,14 +31,17 @@ + ham[lower + offset : upper + offset] + + slice[::, ::] +-slice[ ++( ++ slice[ + # A + : + # B + : + # C + ] +-slice[ ++) ++( ++ slice[ + # A + 1: + # B +@@ -46,8 +49,10 @@ + # C + 3 + ] ++) + +-slice[ ++( ++ slice[ + # A + 1 + + 2 : +@@ -56,4 +61,11 @@ # C 4 ] -x[1:2:3] # A # B # C -+x[ ++) ++( ++ x[ + 1: # A + 2: # B + 3 # C +] ++) ``` ## Ruff Output @@ -124,14 +159,17 @@ ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] ham[lower + offset : upper + offset] slice[::, ::] -slice[ +( + slice[ # A : # B : # C ] -slice[ +) +( + slice[ # A 1: # B @@ -139,8 +177,10 @@ slice[ # C 3 ] +) -slice[ +( + slice[ # A 1 + 2 : @@ -149,11 +189,14 @@ slice[ # C 4 ] -x[ +) +( + x[ 1: # A 2: # B 3 # C ] +) ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap index 95a7d919071269..42d322316dc645 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__torture_py.snap @@ -42,14 +42,13 @@ assert ( ```diff --- Black +++ Ruff -@@ -1,58 +1,33 @@ - importA --( -- () -- << 0 +@@ -2,18 +2,13 @@ + ( + () + << 0 - ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 --) # -+() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 # ++ **101234234242352525425252352352525234890264906820496920680926538059059209922523523525 + ) # assert sort_by_dependency( { @@ -65,12 +64,7 @@ assert ( } ) == ["2a", "2b", "2", "3a", "3b", "3", "1"] - importA - 0 --0 ^ 0 # -+0^0 # - - +@@ -25,34 +20,18 @@ class A: def foo(self): for _ in range(10): @@ -120,7 +114,11 @@ assert ( ```py importA -() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 # +( + () + << 0 + **101234234242352525425252352352525234890264906820496920680926538059059209922523523525 +) # assert sort_by_dependency( { @@ -131,7 +129,7 @@ assert sort_by_dependency( importA 0 -0^0 # +0 ^ 0 # class A: diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_expression_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_expression_py.snap new file mode 100644 index 00000000000000..6479533300931a --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__expression__binary_expression_py.snap @@ -0,0 +1,92 @@ +--- +source: crates/ruff_python_formatter/src/lib.rs +expression: snapshot +--- +## Input +```py +(aaaaaaaa + + # trailing operator comment + b # trailing right comment +) + + +(aaaaaaaa # trailing left comment + + # trailing operator comment + # leading right comment + b +) + + +# Black breaks the right side first for the following expressions: +aaaaaaaaaaaaaa + caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal(argument1, argument2, argument3) +aaaaaaaaaaaaaa + [bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee] +aaaaaaaaaaaaaa + (bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee) +aaaaaaaaaaaaaa + { key1:bbbbbbbbbbbbbbbbbbbbbb, key2: ccccccccccccccccccccc, key3: dddddddddddddddd, key4: eeeeeee } +aaaaaaaaaaaaaa + { bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee } +aaaaaaaaaaaaaa + [a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ] +aaaaaaaaaaaaaa + (a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ) +aaaaaaaaaaaaaa + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb} + + +# But only for expressions that have a statement parent. +not (aaaaaaaaaaaaaa + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}) +[a + [bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] in c ] +``` + + + +## Output +```py +( + aaaaaaaa + + b # trailing operator comment # trailing right comment +) + + +( + aaaaaaaa # trailing left comment + + # trailing operator comment + # leading right comment + b +) +# Black breaks the right side first for the following expressions: +( + aaaaaaaaaaaaaa + + caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaal(argument1, argument2, argument3) +) +( + aaaaaaaaaaaaaa + + [bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee] +) +( + aaaaaaaaaaaaaa + + (bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee) +) +( + aaaaaaaaaaaaaa + + { key1:bbbbbbbbbbbbbbbbbbbbbb, key2: ccccccccccccccccccccc, key3: dddddddddddddddd, key4: eeeeeee } +) +( + aaaaaaaaaaaaaa + + { bbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccc, dddddddddddddddd, eeeeeee } +) +( + aaaaaaaaaaaaaa + + [a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ] +) +( + aaaaaaaaaaaaaa + + (a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ) +) +( + aaaaaaaaaaaaaa + + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb} +) +# But only for expressions that have a statement parent. +( + not (aaaaaaaaaaaaaa + {a for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}) +) +[a + [bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] in c ] +``` + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__while_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__statement__while_py.snap similarity index 100% rename from crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__while_py.snap rename to crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__ruff_test__statement__while_py.snap diff --git a/crates/ruff_python_formatter/src/statement/stmt_expr.rs b/crates/ruff_python_formatter/src/statement/stmt_expr.rs index 691411c762a173..07442daabdadc5 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_expr.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_expr.rs @@ -1,3 +1,4 @@ +use crate::expression::Parentheses; use crate::prelude::*; use crate::FormatNodeRule; use rustpython_parser::ast::StmtExpr; @@ -9,6 +10,6 @@ impl FormatNodeRule for FormatStmtExpr { fn fmt_fields(&self, item: &StmtExpr, f: &mut PyFormatter) -> FormatResult<()> { let StmtExpr { value, .. } = item; - value.format().fmt(f) + value.format().with_options(Parentheses::Optional).fmt(f) } } diff --git a/crates/ruff_python_formatter/src/statement/stmt_while.rs b/crates/ruff_python_formatter/src/statement/stmt_while.rs index 7ff55a21de8c01..13c9d3aa254085 100644 --- a/crates/ruff_python_formatter/src/statement/stmt_while.rs +++ b/crates/ruff_python_formatter/src/statement/stmt_while.rs @@ -1,5 +1,5 @@ use crate::comments::{leading_alternate_branch_comments, trailing_comments}; -use crate::expression::maybe_parenthesize::maybe_parenthesize; +use crate::expression::Parentheses; use crate::prelude::*; use crate::FormatNodeRule; use ruff_formatter::write; @@ -33,7 +33,7 @@ impl FormatNodeRule for FormatStmtWhile { [ text("while"), space(), - maybe_parenthesize(test), + test.format().with_options(Parentheses::IfBreaks), text(":"), trailing_comments(trailing_condition_comments), block_indent(&body.format()) diff --git a/crates/ruff_python_formatter/src/trivia.rs b/crates/ruff_python_formatter/src/trivia.rs index e1224883b0b09d..1ffd3f39dfd581 100644 --- a/crates/ruff_python_formatter/src/trivia.rs +++ b/crates/ruff_python_formatter/src/trivia.rs @@ -10,8 +10,8 @@ use ruff_text_size::{TextLen, TextRange, TextSize}; /// /// Returns `None` if the range is empty or only contains trivia (whitespace or comments). pub(crate) fn find_first_non_trivia_character_in_range( - code: &str, range: TextRange, + code: &str, ) -> Option<(TextSize, char)> { let rest = &code[range]; let mut char_iter = rest.chars(); @@ -40,8 +40,63 @@ pub(crate) fn find_first_non_trivia_character_in_range( None } +pub(crate) fn find_first_non_trivia_character_after( + offset: TextSize, + code: &str, +) -> Option<(TextSize, char)> { + find_first_non_trivia_character_in_range(TextRange::new(offset, code.text_len()), code) +} + +pub(crate) fn find_first_non_trivia_character_before( + offset: TextSize, + code: &str, +) -> Option<(TextSize, char)> { + let head = &code[TextRange::up_to(offset)]; + let mut char_iter = head.chars(); + + while let Some(c) = char_iter.next_back() { + match c { + c if is_python_whitespace(c) => { + continue; + } + + // Empty comment + '#' => continue, + + non_trivia_character => { + // Non trivia character but we don't know if it is a comment or not. Consume all characters + // until the start of the line and track if the last non-whitespace character was a `#`. + let mut is_comment = false; + + let first_non_trivia_offset = char_iter.as_str().text_len(); + + while let Some(c) = char_iter.next_back() { + match c { + '#' => { + is_comment = true; + } + '\n' | '\r' => { + if !is_comment { + return Some((first_non_trivia_offset, non_trivia_character)); + } + } + + c => { + if !is_python_whitespace(c) { + is_comment = false; + } + } + } + } + } + } + } + + None +} + /// Returns the number of newlines between `offset` and the first non whitespace character in the source code. -pub(crate) fn lines_before(code: &str, offset: TextSize) -> u32 { +pub(crate) fn lines_before(offset: TextSize, code: &str) -> u32 { let head = &code[TextRange::up_to(offset)]; let mut newlines = 0u32; @@ -68,7 +123,7 @@ pub(crate) fn lines_before(code: &str, offset: TextSize) -> u32 { } /// Counts the empty lines between `offset` and the first non-whitespace character. -pub(crate) fn lines_after(code: &str, offset: TextSize) -> u32 { +pub(crate) fn lines_after(offset: TextSize, code: &str) -> u32 { let rest = &code[usize::from(offset)..]; let mut newlines = 0; @@ -98,33 +153,33 @@ mod tests { #[test] fn lines_before_empty_string() { - assert_eq!(lines_before("", TextSize::new(0)), 0); + assert_eq!(lines_before(TextSize::new(0), ""), 0); } #[test] fn lines_before_in_the_middle_of_a_line() { - assert_eq!(lines_before("a = 20", TextSize::new(4)), 0); + assert_eq!(lines_before(TextSize::new(4), "a = 20"), 0); } #[test] fn lines_before_on_a_new_line() { - assert_eq!(lines_before("a = 20\nb = 10", TextSize::new(7)), 1); + assert_eq!(lines_before(TextSize::new(7), "a = 20\nb = 10"), 1); } #[test] fn lines_before_multiple_leading_newlines() { - assert_eq!(lines_before("a = 20\n\r\nb = 10", TextSize::new(9)), 2); + assert_eq!(lines_before(TextSize::new(9), "a = 20\n\r\nb = 10"), 2); } #[test] fn lines_before_with_comment_offset() { - assert_eq!(lines_before("a = 20\n# a comment", TextSize::new(8)), 0); + assert_eq!(lines_before(TextSize::new(8), "a = 20\n# a comment"), 0); } #[test] fn lines_before_with_trailing_comment() { assert_eq!( - lines_before("a = 20 # some comment\nb = 10", TextSize::new(22)), + lines_before(TextSize::new(22), "a = 20 # some comment\nb = 10"), 1 ); } @@ -132,40 +187,40 @@ mod tests { #[test] fn lines_before_with_comment_only_line() { assert_eq!( - lines_before("a = 20\n# some comment\nb = 10", TextSize::new(22)), + lines_before(TextSize::new(22), "a = 20\n# some comment\nb = 10"), 1 ); } #[test] fn lines_after_empty_string() { - assert_eq!(lines_after("", TextSize::new(0)), 0); + assert_eq!(lines_after(TextSize::new(0), ""), 0); } #[test] fn lines_after_in_the_middle_of_a_line() { - assert_eq!(lines_after("a = 20", TextSize::new(4)), 0); + assert_eq!(lines_after(TextSize::new(4), "a = 20"), 0); } #[test] fn lines_after_before_a_new_line() { - assert_eq!(lines_after("a = 20\nb = 10", TextSize::new(6)), 1); + assert_eq!(lines_after(TextSize::new(6), "a = 20\nb = 10"), 1); } #[test] fn lines_after_multiple_newlines() { - assert_eq!(lines_after("a = 20\n\r\nb = 10", TextSize::new(6)), 2); + assert_eq!(lines_after(TextSize::new(6), "a = 20\n\r\nb = 10"), 2); } #[test] fn lines_after_before_comment_offset() { - assert_eq!(lines_after("a = 20 # a comment\n", TextSize::new(7)), 0); + assert_eq!(lines_after(TextSize::new(7), "a = 20 # a comment\n"), 0); } #[test] fn lines_after_with_comment_only_line() { assert_eq!( - lines_after("a = 20\n# some comment\nb = 10", TextSize::new(6)), + lines_after(TextSize::new(6), "a = 20\n# some comment\nb = 10"), 1 ); }