mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-14 06:15:13 +00:00
Format UnaryExpr
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary This PR adds basic formatting for unary expressions. <!-- What's the purpose of the change? What does it do, and why? --> ## Test Plan I added a new `unary.py` with custom test cases
This commit is contained in:
parent
3973836420
commit
1336ca601b
13 changed files with 623 additions and 115 deletions
|
@ -323,6 +323,10 @@ fn can_break(expr: &Expr) -> bool {
|
|||
}) => !expressions.is_empty(),
|
||||
Expr::Call(ExprCall { args, keywords, .. }) => !(args.is_empty() && keywords.is_empty()),
|
||||
Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) | Expr::GeneratorExp(_) => true,
|
||||
Expr::UnaryOp(ExprUnaryOp { operand, .. }) => match operand.as_ref() {
|
||||
Expr::BinOp(_) => true,
|
||||
_ => can_break(operand.as_ref()),
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,68 @@
|
|||
use crate::comments::Comments;
|
||||
use crate::comments::{trailing_comments, Comments};
|
||||
use crate::expression::parentheses::{
|
||||
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use rustpython_parser::ast::ExprUnaryOp;
|
||||
use crate::prelude::*;
|
||||
use crate::trivia::{SimpleTokenizer, TokenKind};
|
||||
use crate::FormatNodeRule;
|
||||
use ruff_formatter::FormatContext;
|
||||
use ruff_python_ast::prelude::UnaryOp;
|
||||
use ruff_text_size::{TextLen, TextRange};
|
||||
use rustpython_parser::ast::{ExprUnaryOp, Ranged};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprUnaryOp;
|
||||
|
||||
impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
||||
fn fmt_fields(&self, item: &ExprUnaryOp, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [not_yet_implemented(item)])
|
||||
let ExprUnaryOp {
|
||||
range: _,
|
||||
op,
|
||||
operand,
|
||||
} = item;
|
||||
|
||||
let operator = match op {
|
||||
UnaryOp::Invert => "~",
|
||||
UnaryOp::Not => "not",
|
||||
UnaryOp::UAdd => "+",
|
||||
UnaryOp::USub => "-",
|
||||
};
|
||||
|
||||
text(operator).fmt(f)?;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
|
||||
// Split off the comments that follow after the operator and format them as trailing comments.
|
||||
// ```python
|
||||
// (not # comment
|
||||
// a)
|
||||
// ```
|
||||
let leading_operand_comments = comments.leading_comments(operand.as_ref());
|
||||
let trailing_operator_comments_end =
|
||||
leading_operand_comments.partition_point(|p| p.position().is_end_of_line());
|
||||
let (trailing_operator_comments, leading_operand_comments) =
|
||||
leading_operand_comments.split_at(trailing_operator_comments_end);
|
||||
|
||||
if !trailing_operator_comments.is_empty() {
|
||||
trailing_comments(trailing_operator_comments).fmt(f)?;
|
||||
}
|
||||
|
||||
// Insert a line break if the operand has comments but itself is not parenthesized.
|
||||
// ```python
|
||||
// if (
|
||||
// not
|
||||
// # comment
|
||||
// a)
|
||||
// ```
|
||||
if !leading_operand_comments.is_empty()
|
||||
&& !is_operand_parenthesized(item, f.context().source_code().as_str())
|
||||
{
|
||||
hard_line_break().fmt(f)?;
|
||||
} else if op.is_not() {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
|
||||
operand.format().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +73,37 @@ impl NeedsParentheses for ExprUnaryOp {
|
|||
source: &str,
|
||||
comments: &Comments,
|
||||
) -> Parentheses {
|
||||
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
|
||||
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
|
||||
Parentheses::Optional => {
|
||||
// We preserve the parentheses of the operand. It should not be necessary to break this expression.
|
||||
if is_operand_parenthesized(self, source) {
|
||||
Parentheses::Never
|
||||
} else {
|
||||
Parentheses::Optional
|
||||
}
|
||||
}
|
||||
parentheses => parentheses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_operand_parenthesized(unary: &ExprUnaryOp, source: &str) -> bool {
|
||||
let operator_len = match unary.op {
|
||||
UnaryOp::Invert => '~'.text_len(),
|
||||
UnaryOp::Not => "not".text_len(),
|
||||
UnaryOp::UAdd => '+'.text_len(),
|
||||
UnaryOp::USub => '-'.text_len(),
|
||||
};
|
||||
|
||||
let trivia_range = TextRange::new(unary.range.start() + operator_len, unary.operand.start());
|
||||
|
||||
if let Some(token) = SimpleTokenizer::new(source, trivia_range)
|
||||
.skip_trivia()
|
||||
.next()
|
||||
{
|
||||
debug_assert_eq!(token.kind(), TokenKind::LParen);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ pub(super) fn default_expression_needs_parentheses(
|
|||
}
|
||||
|
||||
/// Configures if the expression should be parenthesized.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum Parenthesize {
|
||||
/// Parenthesize the expression if it has parenthesis in the source.
|
||||
#[default]
|
||||
|
@ -56,11 +56,11 @@ pub enum Parenthesize {
|
|||
}
|
||||
|
||||
impl Parenthesize {
|
||||
const fn is_if_breaks(self) -> bool {
|
||||
pub(crate) const fn is_if_breaks(self) -> bool {
|
||||
matches!(self, Parenthesize::IfBreaks)
|
||||
}
|
||||
|
||||
const fn is_preserve(self) -> bool {
|
||||
pub(crate) const fn is_preserve(self) -> bool {
|
||||
matches!(self, Parenthesize::Preserve)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue