use rustpython_parser::ast::{Constant, Expr, ExprAttribute, ExprConstant}; use ruff_formatter::write; use ruff_python_ast::node::AnyNodeRef; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::prelude::*; use crate::FormatNodeRule; #[derive(Default)] pub struct FormatExprAttribute; impl FormatNodeRule for FormatExprAttribute { fn fmt_fields(&self, item: &ExprAttribute, f: &mut PyFormatter) -> FormatResult<()> { let ExprAttribute { value, range: _, attr, ctx: _, } = item; let needs_parentheses = matches!( value.as_ref(), Expr::Constant(ExprConstant { value: Constant::Int(_) | Constant::Float(_), .. }) ); let comments = f.context().comments().clone(); let dangling_comments = comments.dangling_comments(item); let leading_attribute_comments_start = dangling_comments.partition_point(|comment| comment.line_position().is_end_of_line()); let (trailing_dot_comments, leading_attribute_comments) = dangling_comments.split_at(leading_attribute_comments_start); if needs_parentheses { value.format().with_options(Parentheses::Always).fmt(f)?; } else if let Expr::Attribute(expr_attribute) = value.as_ref() { // We're in a attribute chain (`a.b.c`). The outermost node adds parentheses if // required, the inner ones don't need them so we skip the `Expr` formatting that // normally adds the parentheses. expr_attribute.format().fmt(f)?; } else { value.format().fmt(f)?; } if comments.has_trailing_own_line_comments(value.as_ref()) { hard_line_break().fmt(f)?; } write!( f, [ text("."), trailing_comments(trailing_dot_comments), (!leading_attribute_comments.is_empty()).then_some(hard_line_break()), leading_comments(leading_attribute_comments), attr.format() ] ) } fn fmt_dangling_comments( &self, _node: &ExprAttribute, _f: &mut PyFormatter, ) -> FormatResult<()> { // handle in `fmt_fields` Ok(()) } } impl NeedsParentheses for ExprAttribute { fn needs_parentheses( &self, parent: AnyNodeRef, context: &PyFormatContext, ) -> OptionalParentheses { // Checks if there are any own line comments in an attribute chain (a.b.c). if context .comments() .dangling_comments(self) .iter() .any(|comment| comment.line_position().is_own_line()) || context.comments().has_trailing_own_line_comments(self) { OptionalParentheses::Always } else { self.value.needs_parentheses(parent, context) } } }