diff --git a/crates/ruff_formatter/src/source_code.rs b/crates/ruff_formatter/src/source_code.rs index 91dc47401c..8f2104966f 100644 --- a/crates/ruff_formatter/src/source_code.rs +++ b/crates/ruff_formatter/src/source_code.rs @@ -1,4 +1,4 @@ -use ruff_text_size::TextRange; +use ruff_text_size::{TextRange, TextSize}; use std::fmt::{Debug, Formatter}; /// The source code of a document that gets formatted @@ -68,9 +68,17 @@ impl SourceCodeSlice { &code.text[self.range] } - pub fn range(&self) -> TextRange { + pub const fn range(&self) -> TextRange { self.range } + + pub const fn start(&self) -> TextSize { + self.range.start() + } + + pub const fn end(&self) -> TextSize { + self.range.end() + } } impl Debug for SourceCodeSlice { diff --git a/crates/ruff_python_formatter/src/comments/format.rs b/crates/ruff_python_formatter/src/comments/format.rs new file mode 100644 index 0000000000..f38f7224b9 --- /dev/null +++ b/crates/ruff_python_formatter/src/comments/format.rs @@ -0,0 +1,180 @@ +use crate::context::NodeLevel; +use crate::prelude::*; +use crate::trivia::{lines_after, lines_before}; +use ruff_formatter::{format_args, write}; +use ruff_python_ast::node::AnyNodeRef; +use ruff_python_ast::prelude::AstNode; + +/// Formats the leading comments of a node. +pub(crate) fn leading_comments(node: &T) -> FormatLeadingComments +where + T: AstNode, +{ + FormatLeadingComments { + node: node.as_any_node_ref(), + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct FormatLeadingComments<'a> { + node: AnyNodeRef<'a>, +} + +impl Format> for FormatLeadingComments<'_> { + fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { + let comments = f.context().comments().clone(); + + for comment in comments.leading_comments(self.node) { + let slice = comment.slice(); + + let lines_after_comment = lines_after(f.context().contents(), slice.end()); + write!( + f, + [ + source_text_slice(slice.range(), ContainsNewlines::No), + empty_lines(lines_after_comment) + ] + )?; + + comment.mark_formatted(); + } + + Ok(()) + } +} + +/// Formats the trailing comments of `node` +pub(crate) fn trailing_comments(node: &T) -> FormatTrailingComments +where + T: AstNode, +{ + FormatTrailingComments { + node: node.as_any_node_ref(), + } +} + +pub(crate) struct FormatTrailingComments<'a> { + node: AnyNodeRef<'a>, +} + +impl Format> for FormatTrailingComments<'_> { + fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { + let comments = f.context().comments().clone(); + let mut has_empty_lines_before = false; + + for trailing in comments.trailing_comments(self.node) { + let slice = trailing.slice(); + let content = source_text_slice(slice.range(), ContainsNewlines::No); + + let lines_before_comment = lines_before(f.context().contents(), slice.start()); + has_empty_lines_before |= lines_before_comment > 0; + + if has_empty_lines_before { + // A trailing comment at the end of a body or list + // ```python + // def test(): + // pass + // + // # Some comment + // ``` + write!( + f, + [ + line_suffix(&format_with(|f| { + write!(f, [empty_lines(lines_before_comment), content]) + })), + expand_parent() + ] + )?; + } else { + write!( + f, + [ + line_suffix(&format_args![space(), space(), content]), + expand_parent() + ] + )?; + } + + trailing.mark_formatted(); + } + + Ok(()) + } +} + +/// Formats the dangling comments of `node`. +pub(crate) fn dangling_comments(node: &T) -> FormatDanglingComments +where + T: AstNode, +{ + FormatDanglingComments { + node: node.as_any_node_ref(), + } +} + +pub(crate) struct FormatDanglingComments<'a> { + node: AnyNodeRef<'a>, +} + +impl Format> for FormatDanglingComments<'_> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let comments = f.context().comments().clone(); + + let dangling_comments = comments.dangling_comments(self.node); + + let mut first = true; + for comment in dangling_comments { + if first && comment.position().is_end_of_line() { + write!(f, [space(), space()])?; + } + + write!( + f, + [ + source_text_slice(comment.slice().range(), ContainsNewlines::No), + empty_lines(lines_after(f.context().contents(), comment.slice().end())) + ] + )?; + + comment.mark_formatted(); + + first = false; + } + + Ok(()) + } +} + +// Helper that inserts the appropriate number of empty lines before a comment, depending on the node level. +// Top level: Up to two empty lines +// parenthesized: A single empty line +// other: Up to a single empty line +const fn empty_lines(lines: u32) -> FormatEmptyLines { + FormatEmptyLines { lines } +} + +#[derive(Copy, Clone, Debug)] +struct FormatEmptyLines { + lines: u32, +} + +impl Format> for FormatEmptyLines { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match f.context().node_level() { + NodeLevel::TopLevel => match self.lines { + 0 | 1 => write!(f, [hard_line_break()]), + 2 => write!(f, [empty_line()]), + _ => write!(f, [empty_line(), empty_line()]), + }, + + NodeLevel::Statement => match self.lines { + 0 | 1 => write!(f, [hard_line_break()]), + _ => write!(f, [empty_line()]), + }, + + // Remove all whitespace in parenthesized expressions + NodeLevel::Parenthesized => write!(f, [hard_line_break()]), + } + } +} diff --git a/crates/ruff_python_formatter/src/comments/mod.rs b/crates/ruff_python_formatter/src/comments/mod.rs index 4c8509fd64..fdc5ea2190 100644 --- a/crates/ruff_python_formatter/src/comments/mod.rs +++ b/crates/ruff_python_formatter/src/comments/mod.rs @@ -93,6 +93,7 @@ use std::fmt::Debug; use std::rc::Rc; mod debug; +mod format; mod map; mod node_key; mod placement; @@ -102,6 +103,7 @@ use crate::comments::debug::{DebugComment, DebugComments}; use crate::comments::map::MultiMap; use crate::comments::node_key::NodeRefEqualityKey; use crate::comments::visitor::CommentsVisitor; +pub(crate) use format::{dangling_comments, leading_comments, trailing_comments}; use ruff_formatter::{SourceCode, SourceCodeSlice}; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::source_code::CommentRanges; diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 2cad567f78..21d03fec72 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -8,6 +8,7 @@ pub struct PyFormatContext<'a> { options: SimpleFormatOptions, contents: &'a str, comments: Comments<'a>, + node_level: NodeLevel, } impl<'a> PyFormatContext<'a> { @@ -20,6 +21,7 @@ impl<'a> PyFormatContext<'a> { options, contents, comments, + node_level: NodeLevel::TopLevel, } } @@ -33,6 +35,15 @@ impl<'a> PyFormatContext<'a> { Locator::new(self.contents) } + #[allow(unused)] + pub(crate) fn set_node_level(&mut self, level: NodeLevel) { + self.node_level = level; + } + + pub(crate) fn node_level(&self) -> NodeLevel { + self.node_level + } + #[allow(unused)] pub(crate) fn comments(&self) -> &Comments<'a> { &self.comments @@ -56,7 +67,24 @@ impl Debug for PyFormatContext<'_> { f.debug_struct("PyFormatContext") .field("options", &self.options) .field("comments", &self.comments.debug(self.source_code())) + .field("node_level", &self.node_level) .field("source", &self.contents) .finish() } } + +/// What's the enclosing level of the outer node. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub(crate) enum NodeLevel { + /// Formatting statements on the module level. + #[default] + TopLevel, + + /// Formatting nodes that are enclosed by a statement. + #[allow(unused)] + Statement, + + /// Formatting nodes that are enclosed in a parenthesized expression. + #[allow(unused)] + Parenthesized, +} diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 3c8a581c73..7a570a173f 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -14,7 +14,7 @@ use ruff_formatter::{ use ruff_python_ast::node::AstNode; use ruff_python_ast::source_code::{CommentRanges, CommentRangesBuilder, Locator}; -use crate::comments::Comments; +use crate::comments::{dangling_comments, leading_comments, trailing_comments, Comments}; use crate::context::PyFormatContext; pub mod cli; @@ -57,31 +57,31 @@ where /// Formats the node's fields. fn fmt_fields(&self, item: &N, f: &mut PyFormatter) -> FormatResult<()>; - /// Formats the [leading comments](crate::comments#leading-comments) of the node. + /// Formats the [leading comments](comments#leading-comments) of the node. /// /// You may want to override this method if you want to manually handle the formatting of comments /// inside of the `fmt_fields` method or customize the formatting of the leading comments. - fn fmt_leading_comments(&self, _node: &N, _f: &mut PyFormatter) -> FormatResult<()> { - Ok(()) + fn fmt_leading_comments(&self, node: &N, f: &mut PyFormatter) -> FormatResult<()> { + leading_comments(node).fmt(f) } - /// Formats the [dangling comments](rome_formatter::comments#dangling-comments) of the node. + /// Formats the [dangling comments](comments#dangling-comments) of the node. /// /// You should override this method if the node handled by this rule can have dangling comments because the /// default implementation formats the dangling comments at the end of the node, which isn't ideal but ensures that /// no comments are dropped. /// /// A node can have dangling comments if all its children are tokens or if all node childrens are optional. - fn fmt_dangling_comments(&self, _node: &N, _f: &mut PyFormatter) -> FormatResult<()> { - Ok(()) + fn fmt_dangling_comments(&self, node: &N, f: &mut PyFormatter) -> FormatResult<()> { + dangling_comments(node).fmt(f) } - /// Formats the [trailing comments](rome_formatter::comments#trailing-comments) of the node. + /// Formats the [trailing comments](comments#trailing-comments) of the node. /// /// You may want to override this method if you want to manually handle the formatting of comments /// inside of the `fmt_fields` method or customize the formatting of the trailing comments. - fn fmt_trailing_comments(&self, _node: &N, _f: &mut PyFormatter) -> FormatResult<()> { - Ok(()) + fn fmt_trailing_comments(&self, node: &N, f: &mut PyFormatter) -> FormatResult<()> { + trailing_comments(node).fmt(f) } } @@ -180,8 +180,10 @@ if True: print( "hi" ) # trailing "#; - let expected = r#"if True: + let expected = r#"# preceding +if True: print( "hi" ) +# trailing "#; let actual = format_module(input)?.as_code().to_string(); assert_eq!(expected, actual); diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap index 8858eb3170..5daf501dc3 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__collections_py.snap @@ -84,23 +84,23 @@ if True: ```diff --- Black +++ Ruff -@@ -1,99 +1,57 @@ +@@ -1,99 +1,61 @@ import core, time, a - from . import A, B, C - --# keeps existing trailing comma + # keeps existing trailing comma from foo import ( bar, ) - --# also keeps existing structure + # also keeps existing structure from foo import ( baz, qux, ) - --# `as` works as well + # `as` works as well from foo import ( xyzzy as magic, ) @@ -155,9 +155,9 @@ if True: - % bar -) - --# looping over a 1-tuple should also not get wrapped +y = {"oneple": (1,),} +assert False, ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" % bar) + # looping over a 1-tuple should also not get wrapped for x in (1,): pass for (x,) in (1,), (2,), (3,): @@ -211,13 +211,16 @@ if True: ```py import core, time, a from . import A, B, C +# keeps existing trailing comma from foo import ( bar, ) +# also keeps existing structure from foo import ( baz, qux, ) +# `as` works as well from foo import ( xyzzy as magic, ) @@ -241,6 +244,7 @@ nested_long_lines = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbb x = {"oneple": (1,)} y = {"oneple": (1,),} assert False, ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa wraps %s" % bar) +# looping over a 1-tuple should also not get wrapped for x in (1,): pass for (x,) in (1,), (2,), (3,): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap index b4e5b4d377..caab87f072 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments2_py.snap @@ -178,7 +178,7 @@ instruction()#comment with bad spacing ```diff --- Black +++ Ruff -@@ -1,39 +1,36 @@ +@@ -1,39 +1,38 @@ from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent, # NOT DRY + MyLovelyCompanyTeamProjectComponent # NOT DRY @@ -188,8 +188,8 @@ instruction()#comment with bad spacing + MyLovelyCompanyTeamProjectComponent as component # DRY ) - --# Please keep __all__ alphabetized within each category. -- + # Please keep __all__ alphabetized within each category. + __all__ = [ # Super-special typing primitives. - "Any", @@ -238,7 +238,7 @@ instruction()#comment with bad spacing # builtin types and objects type, object, -@@ -47,21 +44,20 @@ +@@ -47,21 +46,21 @@ Cheese("Wensleydale"), SubBytes(b"spam"), ] @@ -254,7 +254,7 @@ instruction()#comment with bad spacing - # add_compiler(compilers[(7.1, 64)]) - - --# Comment before function. + # Comment before function. def inline_comments_in_brackets_ruin_everything(): if typedargslist: - parameters.children = [children[0], body, children[-1]] # (1 # )1 @@ -267,7 +267,7 @@ instruction()#comment with bad spacing children[0], body, children[-1], # type: ignore -@@ -73,49 +69,42 @@ +@@ -73,49 +72,42 @@ parameters.children[-1], # )2 ] parameters.children = [parameters.what_if_this_was_actually_long.children[0], body, parameters.children[-1]] # type: ignore @@ -340,7 +340,7 @@ instruction()#comment with bad spacing ] lcomp2 = [ # hello -@@ -127,7 +116,7 @@ +@@ -127,7 +119,7 @@ ] lcomp3 = [ # This one is actually too long to fit in a single line. @@ -349,7 +349,7 @@ instruction()#comment with bad spacing # yup for element in collection.select_elements() # right -@@ -140,34 +129,18 @@ +@@ -140,34 +132,26 @@ # and round and round we go # and round and round we go @@ -374,7 +374,7 @@ instruction()#comment with bad spacing -) # type: Final - - -+CONFIG_FILES = [CONFIG_FILE, ] + SHARED_CONFIG_FILES + USER_CONFIG_FILES ++CONFIG_FILES = [CONFIG_FILE, ] + SHARED_CONFIG_FILES + USER_CONFIG_FILES # type: Final class Test: def _init_host(self, parsed) -> None: - if parsed.hostname is None or not parsed.hostname.strip(): # type: ignore @@ -383,16 +383,16 @@ instruction()#comment with bad spacing pass - - --####################### --### SECTION COMMENT ### --####################### -- -- + ####################### + ### SECTION COMMENT ### + ####################### + + -instruction() # comment with bad spacing -- --# END COMMENTS --# MORE END COMMENTS -+instruction() ++instruction() #comment with bad spacing + + # END COMMENTS + # MORE END COMMENTS ``` ## Ruff Output @@ -404,6 +404,8 @@ from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( MyLovelyCompanyTeamProjectComponent as component # DRY ) +# Please keep __all__ alphabetized within each category. + __all__ = [ # Super-special typing primitives. 'Any', @@ -450,6 +452,7 @@ else: # for compiler in compilers.values(): # add_compiler(compiler) add_compiler(compilers[(7.0, 32)]) +# Comment before function. def inline_comments_in_brackets_ruin_everything(): if typedargslist: parameters.children = [ @@ -537,13 +540,21 @@ short Leaf(token.NEWLINE, '\n') # FIXME: \r\n? ], ) -CONFIG_FILES = [CONFIG_FILE, ] + SHARED_CONFIG_FILES + USER_CONFIG_FILES +CONFIG_FILES = [CONFIG_FILE, ] + SHARED_CONFIG_FILES + USER_CONFIG_FILES # type: Final class Test: def _init_host(self, parsed) -> None: if (parsed.hostname is None or # type: ignore not parsed.hostname.strip()): pass -instruction() +####################### +### SECTION COMMENT ### +####################### + + +instruction() #comment with bad spacing + +# END COMMENTS +# MORE END COMMENTS ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments3_py.snap deleted file mode 100644 index 9499d9093b..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments3_py.snap +++ /dev/null @@ -1,180 +0,0 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: snapshot -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py ---- -## Input - -```py -# The percent-percent comments are Spyder IDE cells. - - -# %% -def func(): - x = """ - a really long string - """ - lcomp3 = [ - # This one is actually too long to fit in a single line. - element.split("\n", 1)[0] - # yup - for element in collection.select_elements() - # right - if element is not None - ] - # Capture each of the exceptions in the MultiError along with each of their causes and contexts - if isinstance(exc_value, MultiError): - embedded = [] - for exc in exc_value.exceptions: - if exc not in _seen: - embedded.append( - # This should be left alone (before) - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) - # This should be left alone (after) - ) - - # everything is fine if the expression isn't nested - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) - - -# %% -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -1,7 +1,3 @@ --# The percent-percent comments are Spyder IDE cells. -- -- --# %% - def func(): - x = """ - a really long string -@@ -43,6 +39,3 @@ - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) -- -- --# %% -``` - -## Ruff Output - -```py -def func(): - x = """ - a really long string - """ - lcomp3 = [ - # This one is actually too long to fit in a single line. - element.split("\n", 1)[0] - # yup - for element in collection.select_elements() - # right - if element is not None - ] - # Capture each of the exceptions in the MultiError along with each of their causes and contexts - if isinstance(exc_value, MultiError): - embedded = [] - for exc in exc_value.exceptions: - if exc not in _seen: - embedded.append( - # This should be left alone (before) - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) - # This should be left alone (after) - ) - - # everything is fine if the expression isn't nested - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) -``` - -## Black Output - -```py -# The percent-percent comments are Spyder IDE cells. - - -# %% -def func(): - x = """ - a really long string - """ - lcomp3 = [ - # This one is actually too long to fit in a single line. - element.split("\n", 1)[0] - # yup - for element in collection.select_elements() - # right - if element is not None - ] - # Capture each of the exceptions in the MultiError along with each of their causes and contexts - if isinstance(exc_value, MultiError): - embedded = [] - for exc in exc_value.exceptions: - if exc not in _seen: - embedded.append( - # This should be left alone (before) - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) - # This should be left alone (after) - ) - - # everything is fine if the expression isn't nested - traceback.TracebackException.from_exception( - exc, - limit=limit, - lookup_lines=lookup_lines, - capture_locals=capture_locals, - # copy the set of _seen exceptions so that duplicates - # shared between sub-exceptions are not omitted - _seen=set(_seen), - ) - - -# %% -``` - - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments5_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments5_py.snap index 8c41235ead..df1348d88f 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments5_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments5_py.snap @@ -86,22 +86,19 @@ if __name__ == "__main__": ```diff --- Black +++ Ruff -@@ -1,13 +1,6 @@ +@@ -1,11 +1,6 @@ while True: if something.changed: -- do.stuff() # trailing comment + do.stuff() # trailing comment - # Comment belongs to the `if` block. - # This one belongs to the `while` block. - - # Should this one, too? I guess so. - --# This one is properly standalone now. -- -+ do.stuff() + # This one is properly standalone now. + for i in range(100): - # first we do this - if i % 33 == 0: -@@ -15,31 +8,17 @@ +@@ -15,27 +10,18 @@ # then we do this print(i) @@ -120,41 +117,37 @@ if __name__ == "__main__": import sys - - --# leading function comment + # leading function comment def wat(): ... - # trailing function comment - - --# SECTION COMMENT -- -- --# leading 1 - @deco1 - # leading 2 - @deco2(with_args=True) -@@ -47,27 +26,14 @@ + # SECTION COMMENT + + +@@ -47,8 +33,6 @@ @deco3 def decorated1(): ... - - --# leading 1 + # leading 1 @deco1 # leading 2 - @deco2(with_args=True) +@@ -56,18 +40,12 @@ # leading function comment def decorated1(): ... - - --# Note: this is fixed in --# Preview.empty_lines_before_class_or_def_with_leading_comments. --# In the current style, the user will have to split those lines by hand. + # Note: this is fixed in + # Preview.empty_lines_before_class_or_def_with_leading_comments. + # In the current style, the user will have to split those lines by hand. some_instruction - - --# This comment should be split from `some_instruction` by two lines but isn't. + # This comment should be split from `some_instruction` by two lines but isn't. def g(): ... - @@ -168,7 +161,9 @@ if __name__ == "__main__": ```py while True: if something.changed: - do.stuff() + do.stuff() # trailing comment +# This one is properly standalone now. + for i in range(100): # first we do this if i % 33 == 0: @@ -185,8 +180,13 @@ try: except OSError: print("problems") import sys +# leading function comment def wat(): ... +# SECTION COMMENT + + +# leading 1 @deco1 # leading 2 @deco2(with_args=True) @@ -194,13 +194,18 @@ def wat(): @deco3 def decorated1(): ... +# leading 1 @deco1 # leading 2 @deco2(with_args=True) # leading function comment def decorated1(): ... +# Note: this is fixed in +# Preview.empty_lines_before_class_or_def_with_leading_comments. +# In the current style, the user will have to split those lines by hand. some_instruction +# This comment should be split from `some_instruction` by two lines but isn't. def g(): ... if __name__ == "__main__": diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap index 7fcf9d102f..24247fc646 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments6_py.snap @@ -131,7 +131,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite ```diff --- Black +++ Ruff -@@ -1,18 +1,11 @@ +@@ -1,18 +1,12 @@ from typing import Any, Tuple - - @@ -141,7 +141,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite pass - - --# test type comments + # test type comments def f(a, b, c, d, e, f, g, h, i): # type: (int, int, int, int, int, int, int, int, int) -> None pass @@ -150,7 +150,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite def f( a, # type: int b, # type: int -@@ -26,8 +19,6 @@ +@@ -26,8 +20,6 @@ ): # type: (...) -> None pass @@ -159,7 +159,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite def f( arg, # type: int *args, # type: *Any -@@ -36,8 +27,6 @@ +@@ -36,8 +28,6 @@ ): # type: (...) -> None pass @@ -168,14 +168,12 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite def f( a, # type: int b, # type: int -@@ -66,23 +55,17 @@ - + element +@@ -67,22 +57,16 @@ + another_element + another_element_with_long_name -- ) # type: int + ) # type: int - - -+ ) def f( x, # not a type comment y, # type: int @@ -193,7 +191,7 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite def func( a=some_list[0], # type: int ): # type: () -> int -@@ -102,17 +85,12 @@ +@@ -102,17 +86,12 @@ c = call( "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" # type: ignore ) @@ -203,16 +201,14 @@ aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*ite "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) - --AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore + AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore - -+AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA call_to_some_function_asdf( foo, [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore ) - --aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] -+aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) + aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] ``` ## Ruff Output @@ -223,6 +219,7 @@ def f( a, # type: int ): pass +# test type comments def f(a, b, c, d, e, f, g, h, i): # type: (int, int, int, int, int, int, int, int, int) -> None pass @@ -275,7 +272,7 @@ def f( + element + another_element + another_element_with_long_name - ) + ) # type: int def f( x, # not a type comment y, # type: int @@ -308,12 +305,12 @@ def func( result = ( # aaa "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) -AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA +AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # type: ignore call_to_some_function_asdf( foo, [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # type: ignore ) -aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments8_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments8_py.snap index 2fcf94c83d..67df15b111 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments8_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments8_py.snap @@ -19,18 +19,24 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_ca ```diff --- Black +++ Ruff -@@ -1,6 +0,0 @@ --# The percent-percent comments are Spyder IDE cells. --# Both `#%%`` and `# %%` are accepted, so `black` standardises --# to the latter. -- --# %% +@@ -2,5 +2,5 @@ + # Both `#%%`` and `# %%` are accepted, so `black` standardises + # to the latter. + -# %% ++#%% + # %% ``` ## Ruff Output ```py +# The percent-percent comments are Spyder IDE cells. +# Both `#%%`` and `# %%` are accepted, so `black` standardises +# to the latter. + +#%% +# %% ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap index 7ca7b4f04b..adc7467642 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments9_py.snap @@ -152,13 +152,13 @@ def bar(): ```diff --- Black +++ Ruff -@@ -1,61 +1,23 @@ --# Test for https://github.com/psf/black/issues/246. -- +@@ -1,60 +1,35 @@ + # Test for https://github.com/psf/black/issues/246. + some = statement - - --# This comment should be split from the statement above by two lines. + # This comment should be split from the statement above by two lines. def function(): pass - @@ -166,9 +166,9 @@ def bar(): some = statement - - --# This multiline comments section --# should be split from the statement --# above by two lines. + # This multiline comments section + # should be split from the statement + # above by two lines. def function(): pass - @@ -176,7 +176,7 @@ def bar(): some = statement - - --# This comment should be split from the statement above by two lines. + # This comment should be split from the statement above by two lines. async def async_function(): pass - @@ -184,16 +184,16 @@ def bar(): some = statement - - --# This comment should be split from the statement above by two lines. + # This comment should be split from the statement above by two lines. class MyClass: pass - - some = statement --# This should be stick to the statement above + # This should be stick to the statement above + - -- --# This should be split from the above by two lines + # This should be split from the above by two lines class MyClassWithComplexLeadingComments: pass - @@ -202,7 +202,7 @@ def bar(): """A docstring.""" - - --# Leading comment after a class with just a docstring + # Leading comment after a class with just a docstring class MyClassAfterAnotherClassWithDocstring: pass - @@ -210,11 +210,10 @@ def bar(): some = statement - - --# leading 1 + # leading 1 @deco1 # leading 2 - # leading 2 extra -@@ -65,12 +27,7 @@ +@@ -65,11 +40,7 @@ # leading 4 def decorated(): pass @@ -223,11 +222,10 @@ def bar(): some = statement - - --# leading 1 + # leading 1 @deco1 # leading 2 - @deco2(with_args=True) -@@ -80,12 +37,7 @@ +@@ -80,11 +51,7 @@ # leading 4 def decorated_with_split_leading_comments(): pass @@ -236,11 +234,10 @@ def bar(): some = statement - - --# leading 1 + # leading 1 @deco1 # leading 2 - @deco2(with_args=True) -@@ -95,66 +47,42 @@ +@@ -95,66 +62,44 @@ # leading 4 that already has an empty line def decorated_with_split_leading_comments(): pass @@ -284,7 +281,7 @@ def bar(): pass - - --# Regression test for https://github.com/psf/black/issues/3454. + # Regression test for https://github.com/psf/black/issues/3454. def foo(): pass - # Trailing comment that belongs to this function @@ -296,7 +293,7 @@ def bar(): pass - - --# Regression test for https://github.com/psf/black/issues/3454. + # Regression test for https://github.com/psf/black/issues/3454. def foo(): pass - # Trailing comment that belongs to this function. @@ -312,26 +309,39 @@ def bar(): ## Ruff Output ```py +# Test for https://github.com/psf/black/issues/246. + some = statement +# This comment should be split from the statement above by two lines. def function(): pass some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. def function(): pass some = statement +# This comment should be split from the statement above by two lines. async def async_function(): pass some = statement +# This comment should be split from the statement above by two lines. class MyClass: pass some = statement +# This should be stick to the statement above + +# This should be split from the above by two lines class MyClassWithComplexLeadingComments: pass class ClassWithDocstring: """A docstring.""" +# Leading comment after a class with just a docstring class MyClassAfterAnotherClassWithDocstring: pass some = statement +# leading 1 @deco1 # leading 2 # leading 2 extra @@ -342,6 +352,7 @@ some = statement def decorated(): pass some = statement +# leading 1 @deco1 # leading 2 @deco2(with_args=True) @@ -352,6 +363,7 @@ some = statement def decorated_with_split_leading_comments(): pass some = statement +# leading 1 @deco1 # leading 2 @deco2(with_args=True) @@ -389,12 +401,14 @@ class MyClass: # More comments. def first_method(self): pass +# Regression test for https://github.com/psf/black/issues/3454. def foo(): pass @decorator1 @decorator2 # fmt: skip def bar(): pass +# Regression test for https://github.com/psf/black/issues/3454. def foo(): pass @decorator1 diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_non_breaking_space_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_non_breaking_space_py.snap index e26a11b3bf..6d7e575a83 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_non_breaking_space_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__comments_non_breaking_space_py.snap @@ -54,11 +54,11 @@ def function(a:int=42): - """This docstring is already formatted - a - b -+result = 1 -+result = ( 1, ) -+result = 1 -+result = 1 -+square = Square(4) ++result = 1 # A simple comment ++result = ( 1, ) # Another one ++result = 1 # type: ignore ++result = 1 # This comment is talking about type: ignore ++square = Square(4) # type: Optional[Square] +def function(a:int=42): + """ This docstring is already formatted + a @@ -76,11 +76,11 @@ def function(a:int=42): from .config import ( ConfigTypeAttributes, Int, Path, # String, # DEFAULT_TYPE_ATTRIBUTES, ) -result = 1 -result = ( 1, ) -result = 1 -result = 1 -square = Square(4) +result = 1 # A simple comment +result = ( 1, ) # Another one +result = 1 # type: ignore +result = 1 # This comment is talking about type: ignore +square = Square(4) # type: Optional[Square] def function(a:int=42): """ This docstring is already formatted a 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 6fa690ac40..b29e94e259 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,14 +109,7 @@ async def wat(): ```diff --- Black +++ Ruff -@@ -1,34 +1,20 @@ --#!/usr/bin/env python3 --# fmt: on --# Some license here. --# --# Has many lines. Many, many lines. --# Many, many, many lines. - """Module docstring. +@@ -8,27 +8,19 @@ Possibly also many, many lines. """ @@ -125,9 +118,8 @@ async def wat(): import sys - import a --from b.c import X # some noqa comment + from b.c import X # some noqa comment - -+from b.c import X try: import fast except ImportError: @@ -145,50 +137,46 @@ async def wat(): def function(default=None): """Docstring comes first. -@@ -45,16 +31,7 @@ +@@ -45,12 +37,8 @@ # This return is also commented for some reason. return default - - --# Explains why we use global state. + # Explains why we use global state. GLOBAL_STATE = {"a": a(1), "b": a(2), "c": a(3)} - - --# Another comment! --# This time two lines. -- -- - class Foo: - """Docstring for class Foo. Example from Sphinx docs.""" + # Another comment! + # This time two lines. -@@ -73,11 +50,6 @@ +@@ -73,8 +61,6 @@ self.spam = 4 """Docstring for instance attribute spam.""" - - --#'

This is pweave!

-- -- - @fast(really=True) - async def wat(): - # This comment, for some reason \ -@@ -89,8 +61,3 @@ - print("A OK", file=sys.stdout) - # Comment between things. - print() -- -- --# Some closing comments. --# Maybe Vim or Emacs directives for formatting. + #'

This is pweave!

+ + +@@ -93,4 +79,4 @@ + + # Some closing comments. + # Maybe Vim or Emacs directives for formatting. -# Who knows. \ No newline at end of file ++# Who knows. ``` ## Ruff Output ```py +#!/usr/bin/env python3 +# fmt: on +# Some license here. +# +# Has many lines. Many, many lines. +# Many, many, many lines. """Module docstring. Possibly also many, many lines. @@ -196,7 +184,7 @@ Possibly also many, many lines. import os.path import sys import a -from b.c import X +from b.c import X # some noqa comment try: import fast except ImportError: @@ -222,7 +210,12 @@ def function(default=None): # This return is also commented for some reason. return default +# Explains why we use global state. GLOBAL_STATE = {"a": a(1), "b": a(2), "c": a(3)} +# Another comment! +# This time two lines. + + class Foo: """Docstring for class Foo. Example from Sphinx docs.""" @@ -241,6 +234,9 @@ class Foo: self.spam = 4 """Docstring for instance attribute spam.""" +#'

This is pweave!

+ + @fast(really=True) async def wat(): # This comment, for some reason \ @@ -252,6 +248,11 @@ async def wat(): print("A OK", file=sys.stdout) # Comment between things. print() + + +# Some closing comments. +# Maybe Vim or Emacs directives for formatting. +# Who knows. ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap deleted file mode 100644 index b69664d3c1..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_no_extra_empty_line_before_eof_py.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: snapshot -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_no_extra_empty_line_before_eof.py ---- -## Input - -```py -# Make sure when the file ends with class's docstring, -# It doesn't add extra blank lines. -class ClassWithDocstring: - """A docstring.""" -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -1,4 +1,2 @@ --# Make sure when the file ends with class's docstring, --# It doesn't add extra blank lines. - class ClassWithDocstring: - """A docstring.""" -``` - -## Ruff Output - -```py -class ClassWithDocstring: - """A docstring.""" -``` - -## Black Output - -```py -# Make sure when the file ends with class's docstring, -# It doesn't add extra blank lines. -class ClassWithDocstring: - """A docstring.""" -``` - - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap index 3bfe82482c..933f6529e7 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__docstring_py.snap @@ -234,7 +234,7 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): ```diff --- Black +++ Ruff -@@ -1,219 +1,155 @@ +@@ -1,219 +1,157 @@ class MyClass: + """ Multiline + class docstring @@ -349,11 +349,11 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): + - And the closing quote is too deep """ - pass -- + pass +def single_line(): + """But with a newline after it! +- -def over_indent(): - """ - This has a shallow indent @@ -387,22 +387,22 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): + """ + "hey yah" """ def and_this(): -- ''' ++ ''' ++ "hey yah"''' ++def multiline_whitespace(): ++ ''' ++ ++ ++ ++ + ''' - "hey yah"''' - - -+ ''' -+ "hey yah"''' - def multiline_whitespace(): +-def multiline_whitespace(): - """ """ - - -+ ''' -+ -+ -+ -+ -+ ''' def oneline_whitespace(): - """ """ - @@ -453,13 +453,13 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): - def docstring_with_inline_tabs_and_tab_indentation(): - """hey -- ++ """hey + - tab separated value - tab at start of line and then a tab separated value - multiple tabs at the beginning and inline - mixed tabs and spaces at beginning. next line has mixed tabs and spaces only. -+ """hey - +- - line ends with some tabs - """ - pass @@ -490,9 +490,9 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): - hey there \ """ - - --# Regression test for #3425 + ''' + hey there \ ''' + # Regression test for #3425 def multiline_backslash_really_long_dont_crash(): """ hey there hello guten tag hi hoow are you ola zdravstvuyte ciao como estas ca va \ """ @@ -509,7 +509,7 @@ def stable_quote_normalization_with_immediate_inner_single_quote(self): "I'm sorry Dave\u2001" - - --# the space below is actually a \u2001, removed in output + # the space below is actually a \u2001, removed in output def my_god_its_full_of_stars_2(): - "I'm sorry Dave" - @@ -675,6 +675,7 @@ def multiline_backslash_1(): def multiline_backslash_2(): ''' hey there \ ''' +# Regression test for #3425 def multiline_backslash_really_long_dont_crash(): """ hey there hello guten tag hi hoow are you ola zdravstvuyte ciao como estas ca va \ """ @@ -683,6 +684,7 @@ def multiline_backslash_3(): already escaped \\ ''' def my_god_its_full_of_stars_1(): "I'm sorry Dave\u2001" +# the space below is actually a \u2001, removed in output def my_god_its_full_of_stars_2(): "I'm sorry Dave " def docstring_almost_at_line_limit(): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap index 26b6b118bb..741cc1757f 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__empty_lines_py.snap @@ -105,11 +105,11 @@ def g(): ```diff --- Black +++ Ruff -@@ -1,11 +1,8 @@ +@@ -1,11 +1,9 @@ """Docstring.""" - - --# leading comment + # leading comment def f(): - NO = "" - SPACE = " " @@ -120,7 +120,7 @@ def g(): t = leaf.type p = leaf.parent # trailing comment -@@ -16,14 +13,19 @@ +@@ -16,14 +14,19 @@ if t == token.COMMENT: # another trailing comment return DOUBLESPACE @@ -140,16 +140,16 @@ def g(): if prevp.type == token.EQUAL: if prevp.parent and prevp.parent.type in { syms.typedargslist, -@@ -43,17 +45,10 @@ +@@ -43,17 +46,14 @@ syms.dictsetmaker, }: return NO - - --############################################################################### --# SECTION BECAUSE SECTIONS --############################################################################### -- + ############################################################################### + # SECTION BECAUSE SECTIONS + ############################################################################### + - def g(): - NO = "" @@ -161,7 +161,7 @@ def g(): t = leaf.type p = leaf.parent -@@ -67,7 +62,7 @@ +@@ -67,7 +67,7 @@ return DOUBLESPACE # Another comment because more comments @@ -176,6 +176,7 @@ def g(): ```py """Docstring.""" +# leading comment def f(): NO = '' SPACE = ' ' @@ -222,6 +223,10 @@ def f(): syms.dictsetmaker, }: return NO +############################################################################### +# SECTION BECAUSE SECTIONS +############################################################################### + def g(): NO = '' SPACE = ' ' 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 f6b997b73c..1a8d43ff9b 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 @@ -401,7 +401,7 @@ last_call() +call(kwarg='hey') +call(arg, kwarg='hey') +call(arg, another, kwarg='hey', **kwargs) -+call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) ++call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 call(*gidgets[:2]) call(a, *gidgets[:2]) call(**self.screen_kwargs) @@ -437,7 +437,7 @@ last_call() -) # type: ignore +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ + ..., List[SomeClass] -+] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) ++] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore slice[0] slice[0:1] slice[0:1:2] @@ -510,7 +510,7 @@ last_call() Ø = set() authors.łukasz.say_thanks() mapping = { -@@ -233,138 +170,82 @@ +@@ -233,138 +170,83 @@ C: 0.1 * (10.0 / 12), D: 0.1 * (10.0 / 12), } @@ -682,7 +682,7 @@ last_call() -) +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa last_call() --# standalone comment at ENDMARKER + # standalone comment at ENDMARKER ``` ## Ruff Output @@ -773,7 +773,7 @@ call(arg) call(kwarg='hey') call(arg, kwarg='hey') call(arg, another, kwarg='hey', **kwargs) -call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) +call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 call(*gidgets[:2]) call(a, *gidgets[:2]) call(**self.screen_kwargs) @@ -801,7 +801,7 @@ xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # t ) xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ ..., List[SomeClass] -] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) +] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore slice[0] slice[0:1] slice[0:1:2] @@ -939,6 +939,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaa bbbb >> bbbb * bbbb aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa last_call() +# standalone comment at ENDMARKER ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap index ab14d29ecd..b7f2c2732c 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff2_py.snap @@ -53,21 +53,16 @@ def test_calculate_fades(): ```diff --- Black +++ Ruff -@@ -1,13 +1,6 @@ +@@ -1,8 +1,6 @@ import pytest - TmSt = 1 TmEx = 2 - --# fmt: off -- --# Test data: --# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] -- - @pytest.mark.parametrize('test', [ + # fmt: off - # Test don't manage the volume -@@ -17,24 +10,18 @@ + # Test data: +@@ -17,19 +15,15 @@ ]) def test_fader(test): pass @@ -87,11 +82,6 @@ def test_calculate_fades(): def test_calculate_fades(): calcs = [ # one is zero/none - (0, 4, 0, 0, 10, 0, 0, 6, 10), - (None, 4, 0, 0, 10, 0, 0, 6, 10), - ] -- --# fmt: on ``` ## Ruff Output @@ -100,6 +90,11 @@ def test_calculate_fades(): import pytest TmSt = 1 TmEx = 2 +# fmt: off + +# Test data: +# Position, Volume, State, TmSt/TmEx/None, [call, [arg1...]] + @pytest.mark.parametrize('test', [ # Test don't manage the volume @@ -124,6 +119,8 @@ def test_calculate_fades(): (0, 4, 0, 0, 10, 0, 0, 6, 10), (None, 4, 0, 0, 10, 0, 0, 6, 10), ] + +# fmt: on ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap index c40098aa52..772a2828ad 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff3_py.snap @@ -30,21 +30,10 @@ x = [ ```diff --- Black +++ Ruff -@@ -1,15 +1,11 @@ --# fmt: off - x = [ - 1, 2, - 3, 4, +@@ -12,4 +12,6 @@ ] --# fmt: on -- --# fmt: off - x = [ - 1, 2, - 3, 4, - ] --# fmt: on -- + # fmt: on + -x = [1, 2, 3, 4] +x = [ + 1, 2, 3, 4 @@ -54,14 +43,20 @@ x = [ ## Ruff Output ```py +# fmt: off x = [ 1, 2, 3, 4, ] +# fmt: on + +# fmt: off x = [ 1, 2, 3, 4, ] +# fmt: on + x = [ 1, 2, 3, 4 ] diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap index ef85726a31..6f14577620 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff4_py.snap @@ -26,10 +26,7 @@ def f(): pass ```diff --- Black +++ Ruff -@@ -1,20 +1,11 @@ --# fmt: off - @test([ - 1, 2, +@@ -4,17 +4,9 @@ 3, 4, ]) # fmt: on @@ -58,6 +55,7 @@ def f(): pass ## Ruff Output ```py +# fmt: off @test([ 1, 2, 3, 4, diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap index 73135d6d22..cab9d6960c 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff5_py.snap @@ -97,62 +97,53 @@ elif unformatted: ```diff --- Black +++ Ruff -@@ -1,4 +1,3 @@ --# Regression test for https://github.com/psf/black/issues/3129. - setup( - entry_points={ - # fmt: off -@@ -9,9 +8,6 @@ +@@ -9,8 +9,6 @@ ] # Includes an formatted indentation. }, ) - - --# Regression test for https://github.com/psf/black/issues/2015. + # Regression test for https://github.com/psf/black/issues/2015. run( # fmt: off - [ -@@ -22,9 +18,6 @@ +@@ -22,8 +20,6 @@ + path, check=True, ) - - --# Regression test for https://github.com/psf/black/issues/3026. + # Regression test for https://github.com/psf/black/issues/3026. def test_func(): # yapf: disable - if unformatted( args ): -@@ -34,9 +27,6 @@ +@@ -34,8 +30,6 @@ return True return False - - --# Regression test for https://github.com/psf/black/issues/2567. + # Regression test for https://github.com/psf/black/issues/2567. if True: # fmt: off - for _ in range( 1 ): -@@ -44,10 +34,7 @@ +@@ -44,9 +38,7 @@ print ( "This won't be formatted" ) print ( "This won't be formatted either" ) else: - print("This will be formatted") - - --# Regression test for https://github.com/psf/black/issues/3184. + print ( "This will be formatted" ) + # Regression test for https://github.com/psf/black/issues/3184. class A: async def call(param): - if param: -@@ -61,27 +48,16 @@ +@@ -61,27 +53,18 @@ elif param[0:4] in ("ZZZZ",): print ( "This won't be formatted either" ) - print("This will be formatted") - - --# Regression test for https://github.com/psf/black/issues/2985. + print ( "This will be formatted" ) + # Regression test for https://github.com/psf/black/issues/2985. class Named(t.Protocol): # fmt: off @property @@ -166,8 +157,8 @@ elif unformatted: - # fmt: on - - --# Regression test for https://github.com/psf/black/issues/3436. + def this_will_be_formatted ( self, **kwargs ) -> Named: ... + # Regression test for https://github.com/psf/black/issues/3436. if x: return x # fmt: off @@ -181,6 +172,7 @@ elif unformatted: ## Ruff Output ```py +# Regression test for https://github.com/psf/black/issues/3129. setup( entry_points={ # fmt: off @@ -191,6 +183,7 @@ setup( ] # Includes an formatted indentation. }, ) +# Regression test for https://github.com/psf/black/issues/2015. run( # fmt: off [ @@ -201,6 +194,7 @@ run( + path, check=True, ) +# Regression test for https://github.com/psf/black/issues/3026. def test_func(): # yapf: disable if unformatted( args ): @@ -210,6 +204,7 @@ def test_func(): return True return False +# Regression test for https://github.com/psf/black/issues/2567. if True: # fmt: off for _ in range( 1 ): @@ -218,6 +213,7 @@ if True: print ( "This won't be formatted either" ) else: print ( "This will be formatted" ) +# Regression test for https://github.com/psf/black/issues/3184. class A: async def call(param): if param: @@ -232,12 +228,14 @@ class A: print ( "This won't be formatted either" ) print ( "This will be formatted" ) +# Regression test for https://github.com/psf/black/issues/2985. class Named(t.Protocol): # fmt: off @property def this_wont_be_formatted ( self ) -> str: ... class Factory(t.Protocol): def this_will_be_formatted ( self, **kwargs ) -> Named: ... +# Regression test for https://github.com/psf/black/issues/3436. if x: return x # fmt: off diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap index fb6ecc96c4..362982ef50 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtonoff_py.snap @@ -199,8 +199,8 @@ d={'a':1, ```diff --- Black +++ Ruff -@@ -1,22 +1,11 @@ --#!/usr/bin/env python3 +@@ -1,20 +1,17 @@ + #!/usr/bin/env python3 import asyncio import sys - @@ -208,28 +208,25 @@ d={'a':1, - -from library import some_connection, some_decorator - --# fmt: off +from library import some_connection, \ + some_decorator + # fmt: off from third_party import (X, Y, Z) --# fmt: on + # fmt: on -f"trigger 3.6 mode" --# Comment 1 -- --# Comment 2 -- -- --# fmt: off +f'trigger 3.6 mode' + # Comment 1 + + # Comment 2 +- + + # fmt: off def func_no_args(): - a; b; c - if True: raise RuntimeError -@@ -38,73 +27,41 @@ - ) +@@ -39,72 +36,41 @@ def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: return text[number:-1] --# fmt: on + # fmt: on -def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): - offset = attr.ib(default=attr.Factory(lambda: _r.uniform(1, 2))) - assert task._cancel_stack[: len(old_stack)] == old_stack @@ -308,7 +305,7 @@ d={'a':1, def example(session): # fmt: off result = session\ -@@ -113,9 +70,6 @@ +@@ -113,9 +79,6 @@ models.Customer.email == email_address)\ .order_by(models.Customer.id.asc())\ .all() @@ -318,23 +315,23 @@ d={'a':1, def off_and_on_without_data(): """All comments here are technically on the same prefix. -@@ -123,12 +77,12 @@ +@@ -123,12 +86,12 @@ """ # fmt: off - # hey, that won't work -- # fmt: on -- pass + #hey, that won't work - - -+ # fmt: on -+ pass ++ ++ + # fmt: on + pass +- +- def on_and_off_broken(): """Another known limitation.""" # fmt: on -@@ -137,21 +91,10 @@ +@@ -137,21 +100,10 @@ and_=indeed . it is not formatted because . the . handling . inside . generate_ignored_nodes() now . considers . multiple . fmt . directives . within . one . prefix @@ -357,7 +354,7 @@ d={'a':1, ) # fmt: off a = ( -@@ -182,24 +125,19 @@ +@@ -182,24 +134,19 @@ re.MULTILINE|re.VERBOSE # fmt: on ) @@ -372,7 +369,7 @@ d={'a':1, + (1, 2, 3, 4), + (5, 6, 7, 8), + (9, 10, 11, 12) -+ } ++ } # yapf: disable cfg.rule( - "Default", - "address", @@ -391,32 +388,36 @@ d={'a':1, # fmt: off xxxxxxx_xxxxxxxxxxxx={ "xxxxxxxx": { -@@ -214,11 +152,9 @@ +@@ -214,7 +161,7 @@ }, }, # fmt: on - xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5, + xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5 ) --# fmt: off + # fmt: off yield 'hello' --# No formatting to the end of the file - l=[1,2,3] - d={'a':1, - 'b':2} ``` ## Ruff Output ```py +#!/usr/bin/env python3 import asyncio import sys from third_party import X, Y, Z from library import some_connection, \ some_decorator +# fmt: off from third_party import (X, Y, Z) +# fmt: on f'trigger 3.6 mode' +# Comment 1 + +# Comment 2 + +# fmt: off def func_no_args(): a; b; c if True: raise RuntimeError @@ -438,6 +439,7 @@ many_args=[1,2,3] ) def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str: return text[number:-1] +# fmt: on def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''): offset = attr.ib(default=attr.Factory( lambda: _r.uniform(1, 2))) assert task._cancel_stack[:len(old_stack)] == old_stack @@ -542,7 +544,7 @@ def single_literal_yapf_disable(): (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) - } + } # yapf: disable cfg.rule( "Default", "address", xxxx_xxxx=["xxx-xxxxxx-xxxxxxxxxx"], @@ -565,7 +567,9 @@ cfg.rule( # fmt: on xxxxxxxxxx_xxxxxxxxxxx_xxxxxxx_xxxxxxxxx=5 ) +# fmt: off yield 'hello' +# No formatting to the end of the file l=[1,2,3] d={'a':1, 'b':2} diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap index 700d4d6688..99d137293e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip2_py.snap @@ -22,14 +22,13 @@ l3 = ["I have", "trailing comma", "so I should be braked",] - "into multiple lines", - "because it is way too long", -] --l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip ++l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] + l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip -l3 = [ - "I have", - "trailing comma", - "so I should be braked", -] -+l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] -+l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] +l3 = ["I have", "trailing comma", "so I should be braked",] ``` @@ -37,7 +36,7 @@ l3 = ["I have", "trailing comma", "so I should be braked",] ```py l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] -l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] +l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip l3 = ["I have", "trailing comma", "so I should be braked",] ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap index 9fa822ada8..6dd878bc1e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip3_py.snap @@ -20,15 +20,14 @@ f = ["This is a very long line that should be formatted into a clearer line ", " ```diff --- Black +++ Ruff -@@ -1,10 +1,5 @@ +@@ -1,10 +1,7 @@ -a = 3 --# fmt: off +a = 3 + # fmt: off b, c = 1, 2 --d = 6 # fmt: skip -+d = 6 + d = 6 # fmt: skip e = 5 --# fmt: on + # fmt: on -f = [ - "This is a very long line that should be formatted into a clearer line ", - "by rearranging.", @@ -40,9 +39,11 @@ f = ["This is a very long line that should be formatted into a clearer line ", " ```py a = 3 +# fmt: off b, c = 1, 2 -d = 6 +d = 6 # fmt: skip e = 5 +# fmt: on f = ["This is a very long line that should be formatted into a clearer line ", "by rearranging."] ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip4_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip4_py.snap index a303ddfc50..745448d84d 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip4_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip4_py.snap @@ -16,15 +16,15 @@ l = [1, 2, 3,] ```diff --- Black +++ Ruff -@@ -1,7 +1,2 @@ +@@ -1,7 +1,3 @@ -a = 2 --# fmt: skip ++a = 2 + # fmt: skip -l = [ - 1, - 2, - 3, -] -+a = 2 +l = [1, 2, 3,] ``` @@ -32,6 +32,7 @@ l = [1, 2, 3,] ```py a = 2 +# fmt: skip l = [1, 2, 3,] ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap deleted file mode 100644 index 46b8920531..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip6_py.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: snapshot -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip6.py ---- -## Input - -```py -class A: - def f(self): - for line in range(10): - if True: - pass # fmt: skip -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -2,4 +2,4 @@ - def f(self): - for line in range(10): - if True: -- pass # fmt: skip -+ pass -``` - -## Ruff Output - -```py -class A: - def f(self): - for line in range(10): - if True: - pass -``` - -## Black Output - -```py -class A: - def f(self): - for line in range(10): - if True: - pass # fmt: skip -``` - - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap index bda5c38d73..e9d2293265 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip7_py.snap @@ -23,18 +23,18 @@ d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasu -c = 9 # fmt: skip -d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip +a = "this is some code" -+b = 5 -+c = 9 -+d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" ++b = 5 #fmt:skip ++c = 9 #fmt: skip ++d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip ``` ## Ruff Output ```py a = "this is some code" -b = 5 -c = 9 -d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" +b = 5 #fmt:skip +c = 9 #fmt: skip +d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip ``` ## Black Output diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip8_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip8_py.snap index adc5c6fa78..358018b4e0 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip8_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip8_py.snap @@ -75,35 +75,32 @@ async def test_async_with(): ```diff --- Black +++ Ruff -@@ -1,17 +1,9 @@ --# Make sure a leading comment is not removed. +@@ -2,15 +2,10 @@ def some_func( unformatted, args ): # fmt: skip print("I am some_func") return 0 - # Make sure this comment is not removed. - - --# Make sure a leading comment is not removed. + # Make sure a leading comment is not removed. async def some_async_func( unformatted, args): # fmt: skip print("I am some_async_func") await asyncio.sleep(1) - - --# Make sure a leading comment is not removed. + # Make sure a leading comment is not removed. class SomeClass( Unformatted, SuperClasses ): # fmt: skip def some_method( self, unformatted, args ): # fmt: skip - print("I am some_method") -@@ -20,9 +12,6 @@ +@@ -20,8 +15,6 @@ async def some_async_method( self, unformatted, args ): # fmt: skip print("I am some_async_method") await asyncio.sleep(1) - - --# Make sure a leading comment is not removed. + # Make sure a leading comment is not removed. if unformatted_call( args ): # fmt: skip print("First branch") - # Make sure this is not removed. -@@ -30,33 +19,21 @@ +@@ -30,33 +23,21 @@ print("Second branch") else : # fmt: skip print("Last branch") @@ -142,12 +139,15 @@ async def test_async_with(): ## Ruff Output ```py +# Make sure a leading comment is not removed. def some_func( unformatted, args ): # fmt: skip print("I am some_func") return 0 +# Make sure a leading comment is not removed. async def some_async_func( unformatted, args): # fmt: skip print("I am some_async_func") await asyncio.sleep(1) +# Make sure a leading comment is not removed. class SomeClass( Unformatted, SuperClasses ): # fmt: skip def some_method( self, unformatted, args ): # fmt: skip print("I am some_method") @@ -156,6 +156,7 @@ class SomeClass( Unformatted, SuperClasses ): # fmt: skip async def some_async_method( self, unformatted, args ): # fmt: skip print("I am some_async_method") await asyncio.sleep(1) +# Make sure a leading comment is not removed. if unformatted_call( args ): # fmt: skip print("First branch") # Make sure this is not removed. diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap deleted file mode 100644 index b4cb7a2292..0000000000 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__fmtskip_py.snap +++ /dev/null @@ -1,42 +0,0 @@ ---- -source: crates/ruff_python_formatter/src/lib.rs -expression: snapshot -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/fmtskip.py ---- -## Input - -```py -a, b = 1, 2 -c = 6 # fmt: skip -d = 5 -``` - -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -1,3 +1,3 @@ - a, b = 1, 2 --c = 6 # fmt: skip -+c = 6 - d = 5 -``` - -## Ruff Output - -```py -a, b = 1, 2 -c = 6 -d = 5 -``` - -## Black Output - -```py -a, b = 1, 2 -c = 6 # fmt: skip -d = 5 -``` - - diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap index 6cc366ed98..a1ca74dcab 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_py.snap @@ -108,8 +108,8 @@ def __await__(): return (yield) ```diff --- Black +++ Ruff -@@ -1,100 +1,51 @@ --#!/usr/bin/env python3 +@@ -1,100 +1,52 @@ + #!/usr/bin/env python3 import asyncio import sys - @@ -244,7 +244,7 @@ def __await__(): return (yield) # trailing standalone comment ) ) -@@ -117,23 +68,18 @@ +@@ -117,23 +69,18 @@ \n? ) $ @@ -276,7 +276,7 @@ def __await__(): return (yield) ) -> A: return ( yield from A( -@@ -142,7 +88,4 @@ +@@ -142,7 +89,4 @@ **kwargs, ) ) @@ -290,6 +290,7 @@ def __await__(): return (yield) ## Ruff Output ```py +#!/usr/bin/env python3 import asyncio import sys from third_party import X, Y, Z diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap index eb3306e510..d5edb4eca1 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__function_trailing_comma_py.snap @@ -74,7 +74,7 @@ some_module.some_function( ```diff --- Black +++ Ruff -@@ -1,114 +1,42 @@ +@@ -1,114 +1,46 @@ -def f( - a, -): @@ -152,12 +152,12 @@ some_module.some_function( - } - - --# 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 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 ): @@ -192,12 +192,12 @@ some_module.some_function( pass - - --# Make sure inner one-element tuple won't explode + # 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 + # Inner trailing comma causes outer to explode some_module.some_function( - argument1, - ( @@ -233,6 +233,8 @@ 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 ): @@ -250,9 +252,11 @@ def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_cr )) ): 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/src/snapshots/ruff_python_formatter__tests__black_test__import_spacing_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__import_spacing_py.snap index e79fdb11b4..b7cde495e0 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__import_spacing_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__import_spacing_py.snap @@ -62,11 +62,11 @@ __all__ = ( ```diff --- Black +++ Ruff -@@ -1,55 +1,30 @@ +@@ -1,55 +1,33 @@ """The asyncio package, tracking PEP 3156.""" - --# flake8: noqa -- + # flake8: noqa + -from logging import WARNING from logging import ( + WARNING @@ -76,19 +76,16 @@ __all__ = ( ) import sys - --# This relies on each of the submodules having an __all__ variable. + # This relies on each of the submodules having an __all__ variable. from .base_events import * from .coroutines import * --from .events import * # comment here + from .events import * # comment here - -+from .events import * from .futures import * --from .locks import * # comment here -+from .locks import * + from .locks import * # comment here from .protocols import * - --from ..runners import * # comment here -+from ..runners import * + from ..runners import * # comment here from ..queues import * from ..streams import * - @@ -136,6 +133,8 @@ __all__ = ( ```py """The asyncio package, tracking PEP 3156.""" +# flake8: noqa + from logging import ( WARNING ) @@ -143,13 +142,14 @@ from logging import ( ERROR, ) import sys +# This relies on each of the submodules having an __all__ variable. from .base_events import * from .coroutines import * -from .events import * +from .events import * # comment here from .futures import * -from .locks import * +from .locks import * # comment here from .protocols import * -from ..runners import * +from ..runners import * # comment here from ..queues import * from ..streams import * from some_library import ( diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__one_element_subscript_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__one_element_subscript_py.snap index 083c187251..2315422ccb 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__one_element_subscript_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__one_element_subscript_py.snap @@ -25,13 +25,12 @@ list_of_types = [tuple[int,],] ```diff --- Black +++ Ruff -@@ -1,22 +1,6 @@ --# We should not treat the trailing comma --# in a single-element subscript. +@@ -2,21 +2,9 @@ + # in a single-element subscript. a: tuple[int,] b = tuple[int,] - --# The magic comma still applies to multi-element subscripts. + # The magic comma still applies to multi-element subscripts. -c: tuple[ - int, - int, @@ -41,15 +40,15 @@ list_of_types = [tuple[int,],] - int, -] - --# Magic commas still work as expected for non-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,], -] -+c: tuple[int, int,] -+d = tuple[int, int,] +small_list = [1,] +list_of_types = [tuple[int,],] ``` @@ -57,10 +56,14 @@ list_of_types = [tuple[int,],] ## Ruff 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/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap index 9001c97e7a..e2e7b79f7e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__power_op_spacing_py.snap @@ -103,17 +103,16 @@ return np.divide( a = 5.0**~4.0 b = 5.0 ** f() c = -(5.0**2.0) -@@ -47,9 +40,6 @@ +@@ -47,8 +40,6 @@ o = settings(max_examples=10**6.0) p = {(k, k**2): v**2.0 for k, v in pairs} q = [10.5**i for i in range(6)] - - --# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) + # WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) if hasattr(view, "sum_of_weights"): return np.divide( # type: ignore[no-any-return] - view.variance, # type: ignore[union-attr] -@@ -57,7 +47,6 @@ +@@ -57,7 +48,6 @@ out=np.full(view.sum_of_weights.shape, np.nan), # type: ignore[union-attr] where=view.sum_of_weights**2 > view.sum_of_weights_squared, # type: ignore[union-attr] ) @@ -168,6 +167,7 @@ n = count <= 10**5.0 o = settings(max_examples=10**6.0) p = {(k, k**2): v**2.0 for k, v in pairs} q = [10.5**i for i in range(6)] +# WE SHOULD DEFINITELY NOT EAT THESE COMMENTS (https://github.com/psf/black/issues/2873) if hasattr(view, "sum_of_weights"): return np.divide( # type: ignore[no-any-return] view.variance, # type: ignore[union-attr] diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__prefer_rhs_split_reformatted_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__prefer_rhs_split_reformatted_py.snap index b0279964fa..e5d90fe119 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__prefer_rhs_split_reformatted_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__prefer_rhs_split_reformatted_py.snap @@ -25,11 +25,10 @@ xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxx ```diff --- Black +++ Ruff -@@ -1,21 +1,5 @@ --# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. -- --# Left hand side fits in a single line but will still be exploded by the --# magic trailing comma. +@@ -2,20 +2,10 @@ + + # Left hand side fits in a single line but will still be exploded by the + # magic trailing comma. -( - first_value, - ( @@ -43,8 +42,8 @@ xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxx arg2, ) - --# Make when when the left side of assignment plus the opening paren "... = (" is --# exactly line length limit + 1, it won't be split like that. + # Make when when the left side of assignment plus the opening paren "... = (" is + # exactly line length limit + 1, it won't be split like that. -xxxxxxxxx_yyy_zzzzzzzz[ - xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1) -] = 1 @@ -54,10 +53,16 @@ xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxx ## Ruff Output ```py +# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. + +# Left hand side fits in a single line but will still be exploded by the +# magic trailing comma. first_value, (m1, m2,), third_value = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv( arg1, arg2, ) +# Make when when the left side of assignment plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)] = 1 ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap index c8c9c2034b..62096f8534 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_await_parens_py.snap @@ -94,16 +94,16 @@ async def main(): ```diff --- Black +++ Ruff -@@ -1,93 +1,56 @@ +@@ -1,93 +1,64 @@ import asyncio - - --# Control example + # Control example async def main(): await asyncio.sleep(1) - - --# Remove brackets for short coroutine/task + # Remove brackets for short coroutine/task async def main(): - await asyncio.sleep(1) - @@ -120,40 +120,31 @@ async def main(): - await asyncio.sleep(1) - - --# Check comments --async def main(): -- await asyncio.sleep(1) # Hello -- -- --async def main(): -- await asyncio.sleep(1) # Hello -- -- + await (asyncio.sleep(1) + ) + # Check comments async def main(): - await asyncio.sleep(1) # Hello - - --# Long lines + await ( # Hello + asyncio.sleep(1) + ) async def main(): -- await asyncio.gather( -- asyncio.sleep(1), -- asyncio.sleep(1), -- asyncio.sleep(1), -- asyncio.sleep(1), -- asyncio.sleep(1), -- asyncio.sleep(1), -- asyncio.sleep(1), +- await asyncio.sleep(1) # Hello +- +- + await ( + asyncio.sleep(1) # Hello - ) ++ ) + async def main(): +- await asyncio.sleep(1) # Hello - - --# Same as above but with magic trailing comma in function ++ await ( ++ asyncio.sleep(1) ++ ) # Hello + # Long lines async def main(): - await asyncio.gather( - asyncio.sleep(1), @@ -163,21 +154,29 @@ async def main(): - asyncio.sleep(1), - asyncio.sleep(1), - asyncio.sleep(1), -+ await ( -+ asyncio.sleep(1) - ) +- ) - - --# Cr@zY Br@ck3Tz ++ await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) + # Same as above but with magic trailing comma in function + async def main(): +- await asyncio.gather( +- asyncio.sleep(1), +- asyncio.sleep(1), +- asyncio.sleep(1), +- asyncio.sleep(1), +- asyncio.sleep(1), +- asyncio.sleep(1), +- asyncio.sleep(1), +- ) +- +- ++ await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) + # Cr@zY Br@ck3Tz async def main(): - await black(1) - - --# Keep brackets around non power operations and nested awaits -+ await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) -+async def main(): -+ await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) -+async def main(): + await ( + ((((((((((((( + ((( ((( @@ -191,6 +190,7 @@ async def main(): + ))) ))) + ))))))))))))) + ) + # Keep brackets around non power operations and nested awaits async def main(): await (set_of_tasks | other_set) - @@ -199,7 +199,7 @@ async def main(): await (await asyncio.sleep(1)) - - --# It's awaits all the way down... + # It's awaits all the way down... async def main(): await (await x) - @@ -226,8 +226,10 @@ async def main(): ```py import asyncio +# Control example async def main(): await asyncio.sleep(1) +# Remove brackets for short coroutine/task async def main(): await (asyncio.sleep(1)) async def main(): @@ -237,6 +239,7 @@ async def main(): async def main(): await (asyncio.sleep(1) ) +# Check comments async def main(): await ( # Hello asyncio.sleep(1) @@ -248,11 +251,14 @@ async def main(): async def main(): await ( asyncio.sleep(1) - ) + ) # Hello +# Long lines async def main(): await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) +# Same as above but with magic trailing comma in function async def main(): await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) +# Cr@zY Br@ck3Tz async def main(): await ( ((((((((((((( @@ -267,10 +273,12 @@ async def main(): ))) ))) ))))))))))))) ) +# Keep brackets around non power operations and nested awaits async def main(): await (set_of_tasks | other_set) async def main(): await (await asyncio.sleep(1)) +# It's awaits all the way down... async def main(): await (await x) async def main(): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap index 973db766a1..f7724c3116 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_except_parens_py.snap @@ -48,8 +48,8 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov ```diff --- Black +++ Ruff -@@ -1,42 +1,24 @@ --# These brackets are redundant, therefore remove. +@@ -1,42 +1,25 @@ + # These brackets are redundant, therefore remove. try: a.something -except AttributeError as err: @@ -100,6 +100,7 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov ## Ruff Output ```py +# These brackets are redundant, therefore remove. try: a.something except (AttributeError) as err: diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap index 313beba4d2..df7d50d6f2 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__remove_for_brackets_py.snap @@ -32,18 +32,18 @@ for (((((k, v))))) in d.items(): ```diff --- Black +++ Ruff -@@ -1,27 +1,11 @@ --# Only remove tuple brackets after `for` +@@ -1,27 +1,15 @@ + # Only remove tuple brackets after `for` -for k, v in d.items(): +for (k, v) in d.items(): print(k, v) - --# Don't touch tuple brackets after `in` + # Don't touch tuple brackets after `in` for module in (core, _unicodefun): if hasattr(module, "_verify_python3_env"): module._verify_python3_env = lambda: None - --# Brackets remain for long for loop lines + # Brackets remain for long for loop lines -for ( - why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, - i_dont_know_but_we_should_still_check_the_behaviour_if_they_do, @@ -60,7 +60,7 @@ for (((((k, v))))) in d.items(): +for (k, v) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): print(k, v) - --# Test deeply nested brackets + # Test deeply nested brackets -for k, v in d.items(): +for (((((k, v))))) in d.items(): print(k, v) @@ -69,15 +69,19 @@ for (((((k, v))))) in d.items(): ## Ruff Output ```py +# Only remove tuple brackets after `for` for (k, v) in d.items(): print(k, v) +# Don't touch tuple brackets after `in` for module in (core, _unicodefun): if hasattr(module, "_verify_python3_env"): module._verify_python3_env = lambda: None +# Brackets remain for long for loop lines for (why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, i_dont_know_but_we_should_still_check_the_behaviour_if_they_do) in d.items(): print(k, v) for (k, v) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): print(k, v) +# Test deeply nested brackets for (((((k, v))))) in d.items(): print(k, v) ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap index 14ddbe8bf6..f80e25a7c9 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__return_annotation_brackets_py.snap @@ -101,45 +101,33 @@ def foo() -> tuple[int, int, int,]: ```diff --- Black +++ Ruff -@@ -1,120 +1,60 @@ --# Control --def double(a: int) -> int: -- return 2 * a -- -- --# Remove the brackets --def double(a: int) -> int: -- return 2 * a -- -- --# Some newline variations --def double(a: int) -> int: -- return 2 * a -- -- --def double(a: int) -> int: -- return 2 * a -- -- +@@ -1,120 +1,70 @@ + # Control def double(a: int) -> int: - return 2 * a - - --# Don't lose the comments --def double(a: int) -> int: # Hello -- return 2 * a -- -- --def double(a: int) -> int: # Hello -- return 2 * a -- -- --# Really long annotations --def foo() -> ( -- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds + return 2*a + # Remove the brackets +-def double(a: int) -> int: +- return 2 * a +- +- +def double(a: int) -> (int): + return 2*a + # Some newline variations +-def double(a: int) -> int: +- return 2 * a +- +- +-def double(a: int) -> int: +- return 2 * a +- +- +-def double(a: int) -> int: +- return 2 * a +- +- +def double(a: int) -> ( + int): + return 2*a @@ -150,17 +138,24 @@ def foo() -> tuple[int, int, int,]: + int +): + return 2*a + # Don't lose the comments +-def double(a: int) -> int: # Hello +- return 2 * a +- +- +-def double(a: int) -> int: # Hello +- return 2 * a +- +- +def double(a: int) -> ( # Hello + int - ): -- return 2 -- -- ++): + return 2*a +def double(a: int) -> ( + int # Hello +): + return 2*a + # Really long annotations def foo() -> ( intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds ): @@ -169,10 +164,15 @@ def foo() -> tuple[int, int, int,]: - -def foo() -> ( - intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds -- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds -): +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: -+ return 2 + return 2 +- +- +-def foo() -> ( +- intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +- | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +-): +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: return 2 - @@ -198,7 +198,7 @@ def foo() -> tuple[int, int, int,]: return 2 - - --# Split args but no need to split return + # Split args but no need to split return -def foo( - a: int, - b: int, @@ -208,8 +208,8 @@ def foo() -> tuple[int, int, int,]: return 2 - - --# Deeply nested brackets --# with *interesting* spacing + # Deeply nested brackets + # with *interesting* spacing -def double(a: int) -> int: - return 2 * a - @@ -237,7 +237,7 @@ def foo() -> tuple[int, int, int,]: return 2 - - --# Return type with commas + # Return type with commas -def foo() -> tuple[int, int, int]: - return 2 - @@ -253,7 +253,9 @@ def foo() -> tuple[int, int, int,]: return 2 - - --# Magic trailing comma example ++def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]: ++ return 2 + # Magic trailing comma example -def foo() -> ( - tuple[ - int, @@ -261,8 +263,6 @@ def foo() -> tuple[int, int, int,]: - int, - ] -): -+def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]: -+ return 2 +def foo() -> tuple[int, int, int,]: return 2 ``` @@ -270,10 +270,13 @@ def foo() -> tuple[int, int, int,]: ## Ruff Output ```py +# Control def double(a: int) -> int: return 2*a +# Remove the brackets def double(a: int) -> (int): return 2*a +# Some newline variations def double(a: int) -> ( int): return 2*a @@ -284,6 +287,7 @@ def double(a: int) -> ( int ): return 2*a +# Don't lose the comments def double(a: int) -> ( # Hello int ): @@ -292,6 +296,7 @@ def double(a: int) -> ( int # Hello ): return 2*a +# Really long annotations def foo() -> ( intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds ): @@ -304,8 +309,11 @@ def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasd return 2 def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: return 2 +# Split args but no need to split return def foo(a: int, b: int, c: int,) -> int: return 2 +# Deeply nested brackets +# with *interesting* spacing def double(a: int) -> (((((int))))): return 2*a def double(a: int) -> ( @@ -322,12 +330,14 @@ def foo() -> ( ) )): return 2 +# Return type with commas def foo() -> ( tuple[int, int, int] ): return 2 def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]: return 2 +# Magic trailing comma example def foo() -> tuple[int, int, int,]: return 2 ``` diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap index b2dbb81ba6..fbac7ed57e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__skip_magic_trailing_comma_py.snap @@ -60,31 +60,31 @@ func( ```diff --- Black +++ Ruff -@@ -1,25 +1,35 @@ --# We should not remove the trailing comma in a single-element subscript. +@@ -1,25 +1,40 @@ + # 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. + # But commas in multiple element subscripts should be removed. -c: tuple[int, int] -d = tuple[int, int] - --# Remove commas for non-subscripts. ++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 -+c: tuple[int, int,] -+d = tuple[int, int,] +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. + # Trailing commas in multiple chained non-nested parens. -zero(one).two(three).four(five) - -func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) @@ -123,15 +123,20 @@ func( ## Ruff 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( 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 d13a796fed..ee4cb215cc 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,7 +76,7 @@ x[ ```diff --- Black +++ Ruff -@@ -17,19 +17,12 @@ +@@ -17,19 +17,14 @@ slice[not so_simple : 1 < val <= 10] slice[(1 for i in range(42)) : x] slice[:: [i for i in range(42)]] @@ -86,17 +86,17 @@ x[ slice[await x : [i async for i in arange(42)] : 42] - - --# These are from PEP-8: + # These are from PEP-8: ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] --# ham[lower+offset : upper+offset] + # ham[lower+offset : upper+offset] ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] ham[lower + offset : upper + offset] - slice[::, ::] slice[ # A -@@ -46,7 +39,6 @@ +@@ -46,7 +41,6 @@ # C 3 ] @@ -104,7 +104,7 @@ x[ slice[ # A 1 -@@ -56,4 +48,8 @@ +@@ -56,4 +50,8 @@ # C 4 ] @@ -140,8 +140,10 @@ slice[(1 for i in range(42)) : x] slice[:: [i for i in range(42)]] async def f(): slice[await x : [i async for i in arange(42)] : 42] +# These are from PEP-8: ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] +# ham[lower+offset : upper+offset] ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] ham[lower + offset : upper + offset] slice[::, ::] diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap index 71cd82d86f..be3dbc6901 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__string_prefixes_py.snap @@ -33,9 +33,9 @@ def docstring_multiline(): ```diff --- Black +++ Ruff -@@ -1,19 +1,12 @@ --#!/usr/bin/env python3 -- +@@ -1,19 +1,14 @@ + #!/usr/bin/env python3 + name = "Łukasz" -(f"hello {name}", f"hello {name}") -(b"", b"") @@ -63,6 +63,8 @@ def docstring_multiline(): ## Ruff Output ```py +#!/usr/bin/env python3 + name = "Łukasz" (f"hello {name}", F"hello {name}") (b"", B"") 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 ab2001454a..304ee0a3cb 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 @@ -50,7 +50,7 @@ assert ( - ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 -) # - -+() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 ++() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 # assert sort_by_dependency( { - "1": {"2", "3"}, @@ -70,7 +70,7 @@ assert ( -0 ^ 0 # - - -+0^0 ++0^0 # class A: def foo(self): for _ in range(10): @@ -120,7 +120,7 @@ assert ( ```py importA -() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 +() << 0 ** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525 # assert sort_by_dependency( { "1": {"2", "3"}, "2": {"2a", "2b"}, "3": {"3a", "3b"}, @@ -129,7 +129,7 @@ assert sort_by_dependency( ) == ["2a", "2b", "2", "3a", "3b", "3", "1"] importA 0 -0^0 +0^0 # class A: def foo(self): for _ in range(10): diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap index 6878c50302..73853e4f0e 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__trailing_commas_in_leading_parts_py.snap @@ -46,7 +46,7 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( ```diff --- Black +++ Ruff -@@ -1,31 +1,7 @@ +@@ -1,30 +1,8 @@ -zero( - one, -).two( @@ -61,9 +61,9 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( - arg4, -).func5(arg5) - --# Inner one-element tuple shouldn't explode +zero(one,).two(three,).four(five,) +func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) + # Inner one-element tuple shouldn't explode func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) - -( @@ -76,28 +76,26 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( -) and func2(arg2) - - --# Example from https://github.com/psf/black/issues/3229 +(a, b, c, d,) = func1(arg1) and func2(arg2) + # Example from https://github.com/psf/black/issues/3229 def refresh_token(self, device_family, refresh_token, api_key): return self.orchestration.refresh_token( - data={ -@@ -33,16 +9,10 @@ +@@ -33,15 +11,12 @@ }, api_key=api_key, )["extensions"]["sdk"]["token"] - - --# Edge case where a bug in a working-in-progress version of --# https://github.com/psf/black/pull/3370 causes an infinite recursion. + # Edge case where a bug in a working-in-progress version of + # https://github.com/psf/black/pull/3370 causes an infinite recursion. assert ( long_module.long_class.long_func().another_func() == long_module.long_class.long_func()["some_key"].another_func(arg1) ) - --# Regression test for https://github.com/psf/black/issues/3414. + # Regression test for https://github.com/psf/black/issues/3414. assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( xxxxxxxxx - ).xxxxxxxxxxxxxxxxxx(), ( ``` ## Ruff Output @@ -105,8 +103,10 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( ```py zero(one,).two(three,).four(five,) func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) +# Inner one-element tuple shouldn't explode func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) (a, b, c, d,) = func1(arg1) and func2(arg2) +# Example from https://github.com/psf/black/issues/3229 def refresh_token(self, device_family, refresh_token, api_key): return self.orchestration.refresh_token( data={ @@ -114,10 +114,13 @@ def refresh_token(self, device_family, refresh_token, api_key): }, api_key=api_key, )["extensions"]["sdk"]["token"] +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. assert ( long_module.long_class.long_func().another_func() == long_module.long_class.long_func()["some_key"].another_func(arg1) ) +# Regression test for https://github.com/psf/black/issues/3414. assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( xxxxxxxxx ).xxxxxxxxxxxxxxxxxx(), ( diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__tupleassign_py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__tupleassign_py.snap index 840ef55068..a1a1c9cf2f 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__tupleassign_py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__black_test__tupleassign_py.snap @@ -20,8 +20,8 @@ this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") ```diff --- Black +++ Ruff -@@ -1,12 +1,3 @@ --# This is a standalone comment. +@@ -1,12 +1,5 @@ + # This is a standalone comment. -( - sdfjklsdfsjldkflkjsf, - sdfjsdfjlksdljkfsdlkf, @@ -29,10 +29,10 @@ this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") - sdsfsdfjskdflsfsdf, -) = (1, 2, 3) - --# This is as well. ++sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = 1, 2, 3 + # This is as well. -(this_will_be_wrapped_in_parens,) = struct.unpack(b"12345678901234567890") - -+sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = 1, 2, 3 +this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") (a,) = call() ``` @@ -40,7 +40,9 @@ this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") ## Ruff Output ```py +# This is a standalone comment. sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = 1, 2, 3 +# This is as well. this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") (a,) = call() ``` diff --git a/crates/ruff_python_formatter/src/trivia.rs b/crates/ruff_python_formatter/src/trivia.rs index 24f796a5ca..db99c85198 100644 --- a/crates/ruff_python_formatter/src/trivia.rs +++ b/crates/ruff_python_formatter/src/trivia.rs @@ -68,9 +68,33 @@ pub(crate) fn lines_before(code: &str, offset: TextSize) -> u32 { newlines } +/// Counts the empty lines between `offset` and the first non-whitespace character. +pub(crate) fn lines_after(code: &str, offset: TextSize) -> u32 { + let rest = &code[usize::from(offset)..]; + let mut newlines = 0; + + for (index, c) in rest.char_indices() { + match c { + '\n' => { + newlines += 1; + } + '\r' if rest.as_bytes().get(index + 1).copied() == Some(b'\n') => { + continue; + } + '\r' => { + newlines += 1; + } + c if is_python_whitespace(c) => continue, + _ => break, + } + } + + newlines +} + #[cfg(test)] mod tests { - use crate::trivia::lines_before; + use crate::trivia::{lines_after, lines_before}; use ruff_text_size::TextSize; #[test] @@ -113,4 +137,37 @@ mod tests { 1 ); } + + #[test] + fn lines_after_empty_string() { + 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); + } + + #[test] + fn lines_after_before_a_new_line() { + assert_eq!(lines_after("a = 20\nb = 10", TextSize::new(6)), 1); + } + + #[test] + fn lines_after_multiple_newlines() { + assert_eq!(lines_after("a = 20\n\r\nb = 10", TextSize::new(6)), 2); + } + + #[test] + fn lines_after_before_comment_offset() { + assert_eq!(lines_after("a = 20 # a comment\n", TextSize::new(7)), 0); + } + + #[test] + fn lines_after_with_comment_only_line() { + assert_eq!( + lines_after("a = 20\n# some comment\nb = 10", TextSize::new(6)), + 1 + ); + } }