@@ -10,10 +10,9 @@ use mago_syntax::ast::Expression;
1010use mago_syntax:: ast:: LegacyArray ;
1111use mago_syntax:: ast:: List ;
1212use mago_syntax:: ast:: Node ;
13- use mago_syntax:: ast:: node ;
13+ use mago_syntax:: ast:: NodeKind ;
1414use mago_syntax:: token:: GetPrecedence ;
1515use mago_syntax:: token:: Precedence ;
16- use node:: NodeKind ;
1716
1817use crate :: document:: Document ;
1918use crate :: document:: Group ;
@@ -27,6 +26,38 @@ use crate::internal::utils::is_at_call_like_expression;
2726use crate :: internal:: utils:: is_at_callee;
2827use crate :: internal:: utils:: unwrap_parenthesized;
2928
29+ /// Recursively finds the leftmost expression in a binary tree and checks if it has
30+ /// a leading own-line comment. This is needed for idempotent formatting because
31+ /// comments inside nested parentheses will "float up" to become leading comments
32+ /// after the parens are removed during formatting.
33+ fn has_leading_comment_in_leftmost ( f : & FormatterState , expr : & Expression ) -> bool {
34+ let expr = unwrap_parenthesized ( expr) ;
35+
36+ if f. has_leading_own_line_comment ( expr. span ( ) ) {
37+ return true ;
38+ }
39+
40+ match expr {
41+ Expression :: Binary ( binary) => has_leading_comment_in_leftmost ( f, binary. lhs ) ,
42+ Expression :: Conditional ( Conditional { then : None , condition, .. } ) => {
43+ has_leading_comment_in_leftmost ( f, condition)
44+ }
45+ _ => false ,
46+ }
47+ }
48+
49+ /// Gets the leftmost expression in a binary tree, recursively unwrapping
50+ /// parentheses and following the LHS of binary/elvis expressions.
51+ fn get_leftmost_expression < ' a > ( expr : & ' a Expression < ' a > ) -> & ' a Expression < ' a > {
52+ let expr = unwrap_parenthesized ( expr) ;
53+
54+ match expr {
55+ Expression :: Binary ( binary) => get_leftmost_expression ( binary. lhs ) ,
56+ Expression :: Conditional ( Conditional { then : None , condition, .. } ) => get_leftmost_expression ( condition) ,
57+ _ => expr,
58+ }
59+ }
60+
3061/// An internal-only enum to represent operators that should be formatted
3162/// like binary operators. This allows us to reuse the same complex formatting
3263/// logic for both true `BinaryOperator`s and other constructs like the
@@ -111,6 +142,7 @@ pub(super) fn print_binaryish_expression<'arena>(
111142 operator : BinaryishOperator < ' arena > ,
112143 right : & ' arena Expression < ' arena > ,
113144) -> Document < ' arena > {
145+ let original_right = right;
114146 let left = unwrap_parenthesized ( left) ;
115147 let right = unwrap_parenthesized ( right) ;
116148
@@ -161,7 +193,7 @@ pub(super) fn print_binaryish_expression<'arena>(
161193 ) ;
162194 }
163195
164- let parts = print_binaryish_expression_parts ( f, left, operator, right , is_inside_parenthesis, false ) ;
196+ let parts = print_binaryish_expression_parts ( f, left, operator, original_right , is_inside_parenthesis, false ) ;
165197
166198 if is_inside_parenthesis {
167199 let lhs_is_binary = left. is_binary ( ) ;
@@ -233,11 +265,14 @@ fn print_binaryish_expression_parts<'arena>(
233265 is_nested : bool ,
234266) -> Vec < ' arena , Document < ' arena > > {
235267 let left = unwrap_parenthesized ( left) ;
268+ let original_right = right;
236269 let right = unwrap_parenthesized ( right) ;
270+ let is_original_right_parenthesized = !std:: ptr:: eq ( original_right, right) ;
237271 let should_break = f
238272 . has_comment ( operator. span ( ) , CommentFlags :: Trailing | CommentFlags :: Leading | CommentFlags :: Line )
239273 || f. has_comment ( left. span ( ) , CommentFlags :: Trailing | CommentFlags :: Line )
240- || f. has_leading_own_line_comment ( right. span ( ) ) ;
274+ || f. has_leading_own_line_comment ( right. span ( ) )
275+ || ( is_original_right_parenthesized && has_leading_comment_in_leftmost ( f, original_right) ) ;
241276
242277 let mut should_inline_this_level = !should_break && should_inline_binary_rhs_expression ( f, right, & operator) ;
243278 should_inline_this_level = should_inline_this_level || f. is_in_inlined_binary_chain ;
@@ -288,9 +323,19 @@ fn print_binaryish_expression_parts<'arena>(
288323 _ => true ,
289324 } ;
290325
291- let line_before_operator = f. settings . line_before_binary_operator && !f. has_leading_own_line_comment ( right. span ( ) ) ;
326+ let has_leading_comment_on_right = f. has_leading_own_line_comment ( right. span ( ) )
327+ || ( is_original_right_parenthesized && has_leading_comment_in_leftmost ( f, original_right) ) ;
328+ let line_before_operator = f. settings . line_before_binary_operator && !has_leading_comment_on_right;
292329 let operator_has_leading_comments = f. has_comment ( operator. span ( ) , CommentFlags :: Leading ) ;
293330
331+ let leftmost_leading_comments =
332+ if is_original_right_parenthesized && has_leading_comment_in_leftmost ( f, original_right) {
333+ let leftmost = get_leftmost_expression ( original_right) ;
334+ f. print_leading_comments ( leftmost. span ( ) )
335+ } else {
336+ None
337+ } ;
338+
294339 let right_document = vec ! [
295340 in f. arena;
296341 if operator_has_leading_comments || ( line_before_operator && !should_inline_this_level) {
@@ -304,8 +349,14 @@ fn print_binaryish_expression_parts<'arena>(
304349 } else {
305350 Document :: Line ( if has_space_around { Line :: default ( ) } else { Line :: soft( ) } )
306351 } ,
307- if should_inline_this_level {
308- Document :: Group ( Group :: new( vec![ in f. arena; right. format( f) ] ) )
352+ if let Some ( comments) = leftmost_leading_comments {
353+ if should_inline_this_level {
354+ Document :: Array ( vec![ in f. arena; comments, Document :: Group ( Group :: new( vec![ in f. arena; right. format( f) ] ) ) ] )
355+ } else {
356+ Document :: Array ( vec![ in f. arena; comments, right. format( f) ] )
357+ }
358+ } else if should_inline_this_level {
359+ Document :: Group ( Group :: new( vec![ in f. arena; right. format( f) ] ) )
309360 } else {
310361 right. format( f)
311362 } ,
0 commit comments