mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 20:42:10 +00:00
Format trailing operator comments as dangling (#7427)
## Summary Given a trailing operator comment in a unary expression, like: ```python if ( not # comment a): ... ``` We were attaching these to the operand (`a`), but formatting them in the unary operator via special handling. Parents shouldn't format the comments of their children, so this instead attaches them as dangling comments on the unary expression. (No intended change in formatting.)
This commit is contained in:
parent
f4d50a2aec
commit
cc9e84c144
4 changed files with 73 additions and 37 deletions
|
@ -1,11 +1,11 @@
|
|||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::ExprUnaryOp;
|
||||
use ruff_python_ast::UnaryOp;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
use crate::comments::trailing_comments;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::comments::{trailing_comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -29,21 +29,14 @@ impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
|||
token(operator).fmt(f)?;
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
let dangling = comments.dangling(item);
|
||||
|
||||
// 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(operand.as_ref());
|
||||
let trailing_operator_comments_end =
|
||||
leading_operand_comments.partition_point(|p| p.line_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)?;
|
||||
}
|
||||
trailing_comments(dangling).fmt(f)?;
|
||||
|
||||
// Insert a line break if the operand has comments but itself is not parenthesized.
|
||||
// ```python
|
||||
|
@ -52,8 +45,8 @@ impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
|||
// # comment
|
||||
// a)
|
||||
// ```
|
||||
if !leading_operand_comments.is_empty()
|
||||
&& !is_operand_parenthesized(item, f.context().source())
|
||||
if comments.has_leading(operand.as_ref())
|
||||
&& !is_expression_parenthesized(operand.as_ref().into(), f.context().source())
|
||||
{
|
||||
hard_line_break().fmt(f)?;
|
||||
} else if op.is_not() {
|
||||
|
@ -62,6 +55,14 @@ impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
|||
|
||||
operand.format().fmt(f)
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprUnaryOp {
|
||||
|
@ -71,31 +72,10 @@ impl NeedsParentheses for ExprUnaryOp {
|
|||
context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
// We preserve the parentheses of the operand. It should not be necessary to break this expression.
|
||||
if is_operand_parenthesized(self, context.source()) {
|
||||
if is_expression_parenthesized(self.operand.as_ref().into(), context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
OptionalParentheses::Multiline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.start() + operator_len, unary.operand.start());
|
||||
|
||||
if let Some(token) = SimpleTokenizer::new(source, trivia_range)
|
||||
.skip_trivia()
|
||||
.next()
|
||||
{
|
||||
debug_assert_eq!(token.kind(), SimpleTokenKind::LParen);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue