factor out has_parenthesis_between, add some docs

This commit is contained in:
Brent Westbrook 2025-11-14 15:50:33 -05:00
parent e0b0b54b25
commit ca5f4cc96f
No known key found for this signature in database
1 changed files with 51 additions and 30 deletions

View File

@ -835,22 +835,9 @@ impl<'a> Operand<'a> {
Operand::Middle { expression } | Operand::Right { expression, .. } => {
let leading = comments.leading(*expression);
if is_expression_parenthesized((*expression).into(), comments.ranges(), source) {
leading.iter().any(|comment| {
comment.end() <= expression.start()
&& !comment.is_formatted()
&& matches!(
SimpleTokenizer::new(
source,
TextRange::new(comment.end(), expression.start()),
)
.skip_trivia()
.next(),
Some(SimpleToken {
kind: SimpleTokenKind::LParen,
..
})
)
})
leading
.iter()
.any(|comment| has_parenthesis_between(comment, expression, source))
} else {
!leading.is_empty()
}
@ -878,6 +865,53 @@ impl<'a> Operand<'a> {
}
}
/// Returns `true` if a left parenthesis is the first non-trivia token found between `comment` and
/// `expression`.
///
/// This can be used to detect unparenthesized leading comments, for example:
///
/// ```py
/// # leading comment
/// (
/// expression
/// )
/// ```
fn has_parenthesis_between(comment: &SourceComment, expression: &Expr, source: &str) -> bool {
// We adjust the comment placement for unary operators to avoid splitting the operator and its
// operand as in:
//
// ```py
// (
// not
// # comment
// True
// )
// ```
//
// but making these leading comments means that the comment range falls after the start of the
// expression.
//
// This case can only occur when the comment is after the operator, so it's safe to return
// `false` here. The comment will definitely be parenthesized if the the operator is.
//
// Unary operators are the only known case of this, so `debug_assert!` that it stays that way.
debug_assert!(
expression.is_unary_op_expr() || comment.end() <= expression.start(),
"Expected leading comment to come before its expression",
);
comment.end() <= expression.start()
&& !comment.is_formatted()
&& matches!(
SimpleTokenizer::new(source, TextRange::new(comment.end(), expression.start()),)
.skip_trivia()
.next(),
Some(SimpleToken {
kind: SimpleTokenKind::LParen,
..
})
)
}
impl Format<PyFormatContext<'_>> for Operand<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let expression = self.expression();
@ -923,20 +957,7 @@ impl Format<PyFormatContext<'_>> for Operand<'_> {
let leading_before_parentheses_end = leading
.iter()
.rposition(|comment| {
comment.end() <= expression.start()
&& comment.is_unformatted()
&& matches!(
SimpleTokenizer::new(
f.context().source(),
TextRange::new(comment.end(), expression.start()),
)
.skip_trivia()
.next(),
Some(SimpleToken {
kind: SimpleTokenKind::LParen,
..
})
)
has_parenthesis_between(comment, expression, f.context().source())
})
.map_or(0, |position| position + 1);