Format StmtTry (#5222)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
David Szotten 2023-06-28 11:02:15 +01:00 committed by GitHub
parent 9e2fd0c620
commit 1979103ec0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 583 additions and 123 deletions

View file

@ -0,0 +1,72 @@
try:
...
except:
...
try:
...
except (KeyError): # should remove brackets and be a single line
...
try: # try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try: # try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try:
...
except:
...
try:
...
except (Exception, Exception, Exception, Exception, Exception, Exception, Exception) as exc: # splits exception over multiple lines
...
try:
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2

View file

@ -29,6 +29,7 @@ pub(super) fn place_comment<'a>(
handle_trailing_body_comment,
handle_trailing_end_of_line_body_comment,
handle_trailing_end_of_line_condition_comment,
handle_trailing_end_of_line_except_comment,
handle_module_level_own_line_comment_before_class_or_function_comment,
handle_arguments_separator_comment,
handle_trailing_binary_expression_left_or_operator_comment,
@ -158,44 +159,52 @@ fn handle_in_between_except_handlers_or_except_handler_and_else_or_finally_comme
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if comment.line_position().is_end_of_line() || comment.following_node().is_none() {
if comment.line_position().is_end_of_line() {
return CommentPlacement::Default(comment);
}
if let Some(AnyNodeRef::ExceptHandlerExceptHandler(except_handler)) = comment.preceding_node() {
// it now depends on the indentation level of the comment if it is a leading comment for e.g.
// the following `elif` or indeed a trailing comment of the previous body's last statement.
let comment_indentation =
whitespace::indentation_at_offset(locator, comment.slice().range().start())
.map(str::len)
.unwrap_or_default();
let (Some(AnyNodeRef::ExceptHandlerExceptHandler(preceding_except_handler)), Some(following)) = (comment.preceding_node(), comment.following_node()) else {
return CommentPlacement::Default(comment);
};
if let Some(except_indentation) =
whitespace::indentation(locator, except_handler).map(str::len)
// it now depends on the indentation level of the comment if it is a leading comment for e.g.
// the following `finally` or indeed a trailing comment of the previous body's last statement.
let comment_indentation =
whitespace::indentation_at_offset(locator, comment.slice().range().start())
.map(str::len)
.unwrap_or_default();
let Some(except_indentation) =
whitespace::indentation(locator, preceding_except_handler).map(str::len) else
{
return if comment_indentation <= except_indentation {
// It has equal, or less indent than the `except` handler. It must be a comment
// of the following `finally` or `else` block
//
// ```python
// try:
// pass
// except Exception:
// print("noop")
// # leading
// finally:
// pass
// ```
// Attach it to the `try` statement.
CommentPlacement::dangling(comment.enclosing_node(), comment)
} else {
// Delegate to `handle_trailing_body_comment`
CommentPlacement::Default(comment)
};
}
return CommentPlacement::Default(comment);
};
if comment_indentation > except_indentation {
// Delegate to `handle_trailing_body_comment`
return CommentPlacement::Default(comment);
}
CommentPlacement::Default(comment)
// It has equal, or less indent than the `except` handler. It must be a comment of a subsequent
// except handler or of the following `finally` or `else` block
//
// ```python
// try:
// pass
// except Exception:
// print("noop")
// # leading
// finally:
// pass
// ```
if following.is_except_handler() {
// Attach it to the following except handler (which has a node) as leading
CommentPlacement::leading(following, comment)
} else {
// No following except handler; attach it to the `try` statement.as dangling
CommentPlacement::dangling(comment.enclosing_node(), comment)
}
}
/// Handles own line comments between the last statement and the first statement of two bodies.
@ -668,6 +677,40 @@ fn handle_trailing_end_of_line_condition_comment<'a>(
CommentPlacement::Default(comment)
}
/// Handles end of line comments after the `:` of an except clause
///
/// ```python
/// try:
/// ...
/// except: # comment
/// pass
/// ```
///
/// It attaches the comment as dangling comment to the enclosing except handler.
fn handle_trailing_end_of_line_except_comment<'a>(
comment: DecoratedComment<'a>,
_locator: &Locator,
) -> CommentPlacement<'a> {
let AnyNodeRef::ExceptHandlerExceptHandler(handler) = comment.enclosing_node() else {
return CommentPlacement::Default(comment);
};
// Must be an end of line comment
if comment.line_position().is_own_line() {
return CommentPlacement::Default(comment);
}
let Some(first_body_statement) = handler.body.first() else {
return CommentPlacement::Default(comment);
};
if comment.slice().start() < first_body_statement.range().start() {
CommentPlacement::dangling(comment.enclosing_node(), comment)
} else {
CommentPlacement::Default(comment)
}
}
/// Attaches comments for the positional only arguments separator `/` or the keywords only arguments
/// separator `*` as dangling comments to the enclosing [`Arguments`] node.
///

View file

@ -1,5 +1,9 @@
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
use crate::comments::trailing_comments;
use crate::expression::parentheses::Parenthesize;
use crate::prelude::*;
use crate::{FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use ruff_python_ast::node::AstNode;
use rustpython_parser::ast::ExceptHandlerExceptHandler;
#[derive(Default)]
@ -11,6 +15,43 @@ impl FormatNodeRule<ExceptHandlerExceptHandler> for FormatExceptHandlerExceptHan
item: &ExceptHandlerExceptHandler,
f: &mut PyFormatter,
) -> FormatResult<()> {
write!(f, [not_yet_implemented(item)])
let ExceptHandlerExceptHandler {
range: _,
type_,
name,
body,
} = item;
let comments_info = f.context().comments().clone();
let dangling_comments = comments_info.dangling_comments(item.as_any_node_ref());
write!(f, [text("except")])?;
if let Some(type_) = type_ {
write!(
f,
[space(), type_.format().with_options(Parenthesize::IfBreaks)]
)?;
if let Some(name) = name {
write!(f, [space(), text("as"), space(), name.format()])?;
}
}
write!(
f,
[
text(":"),
trailing_comments(dangling_comments),
block_indent(&body.format())
]
)
}
fn fmt_dangling_comments(
&self,
_node: &ExceptHandlerExceptHandler,
_f: &mut PyFormatter,
) -> FormatResult<()> {
// dangling comments are formatted as part of fmt_fields
Ok(())
}
}

View file

@ -1,17 +1,112 @@
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
use crate::comments;
use crate::comments::leading_alternate_branch_comments;
use crate::comments::SourceComment;
use crate::prelude::*;
use crate::statement::FormatRefWithRule;
use crate::statement::Stmt;
use crate::{FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use rustpython_parser::ast::StmtTry;
use ruff_python_ast::node::AstNode;
use rustpython_parser::ast::{ExceptHandler, Ranged, StmtTry, Suite};
#[derive(Default)]
pub struct FormatStmtTry;
#[derive(Copy, Clone, Default)]
pub struct FormatExceptHandler;
impl FormatRule<ExceptHandler, PyFormatContext<'_>> for FormatExceptHandler {
fn fmt(
&self,
item: &ExceptHandler,
f: &mut Formatter<PyFormatContext<'_>>,
) -> FormatResult<()> {
match item {
ExceptHandler::ExceptHandler(x) => x.format().fmt(f),
}
}
}
impl<'ast> AsFormat<PyFormatContext<'ast>> for ExceptHandler {
type Format<'a> = FormatRefWithRule<
'a,
ExceptHandler,
FormatExceptHandler,
PyFormatContext<'ast>,
> where Self: 'a;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, FormatExceptHandler::default())
}
}
impl FormatNodeRule<StmtTry> for FormatStmtTry {
fn fmt_fields(&self, item: &StmtTry, f: &mut PyFormatter) -> FormatResult<()> {
write!(f, [not_yet_implemented(item)])
let StmtTry {
range: _,
body,
handlers,
orelse,
finalbody,
} = item;
let comments_info = f.context().comments().clone();
let mut dangling_comments = comments_info.dangling_comments(item.as_any_node_ref());
write!(f, [text("try:"), block_indent(&body.format())])?;
let mut previous_node = body.last();
for handler in handlers {
let handler_comments = comments_info.leading_comments(handler);
write!(
f,
[
leading_alternate_branch_comments(handler_comments, previous_node),
&handler.format()
]
)?;
previous_node = match handler {
ExceptHandler::ExceptHandler(handler) => handler.body.last(),
};
}
(previous_node, dangling_comments) =
format_case("else", orelse, previous_node, dangling_comments, f)?;
format_case("finally", finalbody, previous_node, dangling_comments, f)?;
write!(f, [comments::dangling_comments(dangling_comments)])
}
fn fmt_dangling_comments(&self, _node: &StmtTry, _f: &mut PyFormatter) -> FormatResult<()> {
// TODO(konstin): Needs node formatting or this leads to unstable formatting
// dangling comments are formatted as part of fmt_fields
Ok(())
}
}
fn format_case<'a>(
name: &'static str,
body: &Suite,
previous_node: Option<&Stmt>,
dangling_comments: &'a [SourceComment],
f: &mut PyFormatter,
) -> FormatResult<(Option<&'a Stmt>, &'a [SourceComment])> {
Ok(if let Some(last) = body.last() {
let case_comments_start =
dangling_comments.partition_point(|comment| comment.slice().end() <= last.end());
let (case_comments, rest) = dangling_comments.split_at(case_comments_start);
write!(
f,
[leading_alternate_branch_comments(
case_comments,
previous_node
)]
)?;
write!(f, [text(name), text(":"), block_indent(&body.format())])?;
(None, rest)
} else {
(None, dangling_comments)
})
}

View file

@ -108,7 +108,7 @@ async def wat():
```diff
--- Black
+++ Ruff
@@ -9,16 +9,13 @@
@@ -9,16 +9,16 @@
Possibly also many, many lines.
"""
@ -122,15 +122,16 @@ async def wat():
+NOT_YET_IMPLEMENTED_StmtImport
+NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment
-try:
try:
- import fast
-except ImportError:
+ NOT_YET_IMPLEMENTED_StmtImport
except ImportError:
- import slow as fast
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtImport
# Some comment before a function.
@@ -35,7 +32,7 @@
@@ -35,7 +35,7 @@
Possibly many lines.
"""
# FIXME: Some comment about why this function is crap but still in production.
@ -139,7 +140,7 @@ async def wat():
if inner_imports.are_evil():
# Explains why we have this if.
@@ -82,8 +79,7 @@
@@ -82,8 +82,7 @@
async def wat():
# This comment, for some reason \
# contains a trailing backslash.
@ -149,7 +150,7 @@ async def wat():
# Comment after ending a block.
if result:
print("A OK", file=sys.stdout)
@@ -93,4 +89,4 @@
@@ -93,4 +92,4 @@
# Some closing comments.
# Maybe Vim or Emacs directives for formatting.
@ -178,7 +179,10 @@ NOT_YET_IMPLEMENTED_StmtImport
NOT_YET_IMPLEMENTED_StmtImport
NOT_YET_IMPLEMENTED_StmtImportFrom # some noqa comment
NOT_YET_IMPLEMENTED_StmtTry
try:
NOT_YET_IMPLEMENTED_StmtImport
except ImportError:
NOT_YET_IMPLEMENTED_StmtImport
# Some comment before a function.

View file

@ -85,17 +85,9 @@ if __name__ == "__main__":
```diff
--- Black
+++ Ruff
@@ -20,14 +20,9 @@
with open(some_temp_file) as f:
data = f.read()
-try:
- with open(some_other_file) as w:
- w.write(data)
-
-except OSError:
- print("problems")
+NOT_YET_IMPLEMENTED_StmtTry
@@ -27,7 +27,7 @@
except OSError:
print("problems")
-import sys
+NOT_YET_IMPLEMENTED_StmtImport
@ -129,7 +121,12 @@ for i in range(100):
with open(some_temp_file) as f:
data = f.read()
NOT_YET_IMPLEMENTED_StmtTry
try:
with open(some_other_file) as w:
w.write(data)
except OSError:
print("problems")
NOT_YET_IMPLEMENTED_StmtImport

View file

@ -74,7 +74,7 @@ async def test_async_with():
```diff
--- Black
+++ Ruff
@@ -1,62 +1,55 @@
@@ -1,62 +1,61 @@
# Make sure a leading comment is not removed.
-def some_func( unformatted, args ): # fmt: skip
+def some_func(unformatted, args): # fmt: skip
@ -134,12 +134,16 @@ async def test_async_with():
-try : # fmt: skip
- some_call()
+try:
+ # fmt: skip
some_call()
-except UnformattedError as ex: # fmt: skip
- handle_exception()
-finally : # fmt: skip
- finally_call()
+NOT_YET_IMPLEMENTED_StmtTry
+except UnformattedError as ex: # fmt: skip
+ handle_exception() # fmt: skip
+finally:
finally_call()
-with give_me_context( unformatted, args ): # fmt: skip
@ -202,7 +206,13 @@ async def test_async_for():
NOT_YET_IMPLEMENTED_StmtAsyncFor # fmt: skip
NOT_YET_IMPLEMENTED_StmtTry
try:
# fmt: skip
some_call()
except UnformattedError as ex: # fmt: skip
handle_exception() # fmt: skip
finally:
finally_call()
with give_me_context(unformatted, args): # fmt: skip

View file

@ -65,7 +65,7 @@ with hmm_but_this_should_get_two_preceding_newlines():
```diff
--- Black
+++ Ruff
@@ -32,34 +32,20 @@
@@ -32,34 +32,28 @@
if os.name == "posix":
@ -76,18 +76,18 @@ with hmm_but_this_should_get_two_preceding_newlines():
pass
-
elif os.name == "nt":
- try:
try:
- import msvcrt
-
- def i_should_be_followed_by_only_one_newline():
- pass
+ NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtImport
- except ImportError:
-
- def i_should_be_followed_by_only_one_newline():
- pass
def i_should_be_followed_by_only_one_newline():
pass
except ImportError:
-
def i_should_be_followed_by_only_one_newline():
pass
elif False:
-
class IHopeYouAreHavingALovelyDay:
@ -146,7 +146,15 @@ if os.name == "posix":
def i_should_be_followed_by_only_one_newline():
pass
elif os.name == "nt":
NOT_YET_IMPLEMENTED_StmtTry
try:
NOT_YET_IMPLEMENTED_StmtImport
def i_should_be_followed_by_only_one_newline():
pass
except ImportError:
def i_should_be_followed_by_only_one_newline():
pass
elif False:
class IHopeYouAreHavingALovelyDay:

View file

@ -47,77 +47,100 @@ except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.ov
```diff
--- Black
+++ Ruff
@@ -1,42 +1,17 @@
# These brackets are redundant, therefore remove.
-try:
- a.something
-except AttributeError as err:
@@ -2,7 +2,7 @@
try:
a.something
except AttributeError as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtRaise
# This is tuple of exceptions.
# Although this could be replaced with just the exception,
# we do not remove brackets to preserve AST.
-try:
- a.something
-except (AttributeError,) as err:
@@ -10,28 +10,26 @@
try:
a.something
except (AttributeError,) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtRaise
# This is a tuple of exceptions. Do not remove brackets.
-try:
- a.something
-except (AttributeError, ValueError) as err:
try:
a.something
except (AttributeError, ValueError) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtRaise
# Test long variants.
-try:
- a.something
try:
a.something
-except (
- some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error
-) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+except some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error as err:
+ NOT_YET_IMPLEMENTED_StmtRaise
-try:
- a.something
-except (
- some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
-) as err:
try:
a.something
except (
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtRaise
-try:
- a.something
-except (
- some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
- some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
-) as err:
try:
a.something
@@ -39,4 +37,4 @@
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
) as err:
- raise err
+NOT_YET_IMPLEMENTED_StmtTry
+ NOT_YET_IMPLEMENTED_StmtRaise
```
## Ruff Output
```py
# These brackets are redundant, therefore remove.
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except AttributeError as err:
NOT_YET_IMPLEMENTED_StmtRaise
# This is tuple of exceptions.
# Although this could be replaced with just the exception,
# we do not remove brackets to preserve AST.
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except (AttributeError,) as err:
NOT_YET_IMPLEMENTED_StmtRaise
# This is a tuple of exceptions. Do not remove brackets.
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except (AttributeError, ValueError) as err:
NOT_YET_IMPLEMENTED_StmtRaise
# Test long variants.
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error as err:
NOT_YET_IMPLEMENTED_StmtRaise
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except (
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
) as err:
NOT_YET_IMPLEMENTED_StmtRaise
NOT_YET_IMPLEMENTED_StmtTry
try:
a.something
except (
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
) as err:
NOT_YET_IMPLEMENTED_StmtRaise
```
## Black Output

View file

@ -67,22 +67,18 @@ def example8():
```diff
--- Black
+++ Ruff
@@ -8,13 +8,7 @@
async def show_status():
@@ -10,9 +10,7 @@
while True:
- try:
- if report_host:
try:
if report_host:
- data = (
- f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- ).encode()
- except Exception as e:
- pass
+ NOT_YET_IMPLEMENTED_StmtTry
+ data = (NOT_YET_IMPLEMENTED_ExprJoinedStr).encode()
except Exception as e:
pass
def example():
@@ -30,15 +24,11 @@
@@ -30,15 +28,11 @@
def example2():
@ -100,7 +96,7 @@ def example8():
def example4():
@@ -50,35 +40,11 @@
@@ -50,35 +44,11 @@
def example6():
@ -153,7 +149,11 @@ data = (
async def show_status():
while True:
NOT_YET_IMPLEMENTED_StmtTry
try:
if report_host:
data = (NOT_YET_IMPLEMENTED_ExprJoinedStr).encode()
except Exception as e:
pass
def example():

View file

@ -0,0 +1,167 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py
---
## Input
```py
try:
...
except:
...
try:
...
except (KeyError): # should remove brackets and be a single line
...
try: # try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try: # try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try:
...
except:
...
try:
...
except (Exception, Exception, Exception, Exception, Exception, Exception, Exception) as exc: # splits exception over multiple lines
...
try:
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2
```
## Output
```py
try:
...
except:
...
try:
...
except KeyError: # should remove brackets and be a single line
...
try:
# try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try:
# try
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
...
# before except 2
except KeyError as key: # except line 2
...
# in body 2
# before else
else:
...
# before finally
finally:
...
# with line breaks
try:
...
except:
...
try:
...
except (
Exception,
Exception,
Exception,
Exception,
Exception,
Exception,
Exception,
) as exc: # splits exception over multiple lines
...
try:
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2
```