stylua_lib/formatters/
expression.rs

1#[cfg(feature = "luau")]
2use full_moon::ast::luau::{
3    ElseIfExpression, IfExpression, InterpolatedString, InterpolatedStringSegment,
4};
5use full_moon::{
6    ast::{
7        span::ContainedSpan, BinOp, Expression, FunctionCall, Index, Prefix, Suffix, UnOp, Var,
8        VarExpression,
9    },
10    node::Node,
11    tokenizer::{StringLiteralQuoteType, Symbol, Token, TokenReference, TokenType},
12};
13use std::boxed::Box;
14
15#[cfg(feature = "luau")]
16use crate::formatters::{
17    assignment::calculate_hang_level, luau::format_type_assertion,
18    stmt::remove_condition_parentheses, trivia_util::HasInlineComments,
19};
20use crate::{
21    context::{create_indent_trivia, create_newline_trivia, Context},
22    fmt_symbol,
23    formatters::{
24        functions::{
25            format_anonymous_function, format_call, format_function_call, FunctionCallNextNode,
26        },
27        general::{format_contained_span, format_end_token, format_token_reference, EndTokenType},
28        table::format_table_constructor,
29        trivia::{
30            strip_leading_trivia, strip_trivia, FormatTriviaType, UpdateLeadingTrivia,
31            UpdateTrailingTrivia, UpdateTrivia,
32        },
33        trivia_util::{
34            self, contains_comments, prepend_newline_indent, take_leading_comments,
35            take_trailing_comments, trivia_is_newline, CommentSearch, GetLeadingTrivia,
36            GetTrailingTrivia,
37        },
38    },
39    shape::Shape,
40};
41
42#[macro_export]
43macro_rules! fmt_op {
44    ($ctx:expr, $enum:ident, $value:ident, $shape:expr, { $($(#[$inner:meta])* $operator:ident = $output:expr,)+ }, $other:expr) => {
45        match $value {
46            $(
47                $(#[$inner])*
48                $enum::$operator(token) => $enum::$operator(fmt_symbol!($ctx, token, $output, $shape)),
49            )+
50            #[allow(clippy::redundant_closure_call)]
51            other => $other(other),
52        }
53    };
54}
55
56#[derive(Clone, Copy)]
57enum ExpressionContext {
58    /// Standard expression, with no special context
59    Standard,
60    /// The expression originates from a [`Prefix`] node. The special context here is that the expression will
61    /// always be wrapped in parentheses.
62    Prefix,
63    /// The internal expression is being asserted by a type: the `expr` part of `(expr) :: type`.
64    /// If this occurs and `expr` is wrapped in parentheses, we keep the parentheses, such
65    /// as for cases like `(expr) :: any) :: type`
66    #[cfg(feature = "luau")]
67    TypeAssertion,
68
69    /// The internal expression is on the RHS of a binary operation
70    /// e.g. `(not X) and Y` or `(not X) == Y`, where internal_expression = `not X`
71    /// We should keep parentheses in this case to highlight precedence
72    BinaryLHS,
73
74    /// The internal expression is on the LHS of a binary expression involving ^
75    /// e.g. `(-X) ^ Y`
76    /// We need to keep parentheses here because ^ has higher precedence and is right associative
77    /// and removing parentheses changes meaning
78    BinaryLHSExponent,
79
80    /// The internal expression is having a unary operation applied to it: the `expr` part of #expr.
81    /// If this occurs, and `expr` is a type assertion, then we need to keep the parentheses
82    UnaryOrBinary,
83}
84
85pub fn format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp {
86    fmt_op!(ctx, BinOp, binop, shape, {
87        And = " and ",
88        Caret = " ^ ",
89        GreaterThan = " > ",
90        GreaterThanEqual = " >= ",
91        LessThan = " < ",
92        LessThanEqual = " <= ",
93        Minus = " - ",
94        Or = " or ",
95        Percent = " % ",
96        Plus = " + ",
97        Slash = " / ",
98        Star = " * ",
99        TildeEqual = " ~= ",
100        TwoDots = " .. ",
101        TwoEqual = " == ",
102        #[cfg(feature = "lua53")]
103        Ampersand = " & ",
104        #[cfg(any(feature = "luau", feature = "lua53"))]
105        DoubleSlash = " // ",
106        #[cfg(feature = "lua53")]
107        DoubleLessThan = " << ",
108        #[cfg(feature = "lua53")]
109        Pipe = " | ",
110        #[cfg(feature = "lua53")]
111        Tilde = " ~ ",
112    }, |other: &BinOp| match other {
113        #[cfg(feature = "lua53")]
114        BinOp::DoubleGreaterThan(token) => BinOp::DoubleGreaterThan(
115            format_token_reference(ctx, token, shape)
116                .update_trivia(
117                    FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))]),
118                    FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))])
119                )
120        ),
121        other => panic!("unknown node {:?}", other)
122    })
123}
124
125/// Check to determine whether expression parentheses are required, depending on the provided
126/// internal expression contained within the parentheses
127fn check_excess_parentheses(internal_expression: &Expression, context: ExpressionContext) -> bool {
128    match internal_expression {
129        // Parentheses inside parentheses, not necessary
130        Expression::Parentheses { .. } => true,
131        // Check whether the expression relating to the UnOp is safe
132        Expression::UnaryOperator {
133            expression, unop, ..
134        } => {
135            // If the expression is of the format `(not X) and Y` or `(not X) == Y` etc.
136            // Where internal_expression = not X, we should keep the parentheses
137            if let ExpressionContext::BinaryLHSExponent = context {
138                return false;
139            } else if let ExpressionContext::BinaryLHS = context {
140                if let UnOp::Not(_) = unop {
141                    return false;
142                }
143            }
144
145            check_excess_parentheses(expression, context)
146        }
147        // Don't bother removing them if there is a binop, as they may be needed. TODO: can we be more intelligent here?
148        Expression::BinaryOperator { .. } => false,
149
150        // If we have a type assertion, and the context is a unary or binary operation
151        // we should always keep parentheses
152        // [e.g. #(value :: Array<string>) or -(value :: number)]
153        #[cfg(feature = "luau")]
154        Expression::TypeAssertion { .. }
155            if matches!(
156                context,
157                ExpressionContext::UnaryOrBinary
158                    | ExpressionContext::BinaryLHS
159                    | ExpressionContext::BinaryLHSExponent
160            ) =>
161        {
162            false
163        }
164
165        // Internal expression is a function call
166        // We could potentially be culling values, so we should not remove parentheses
167        Expression::FunctionCall(_) => false,
168        Expression::Symbol(token_ref) => {
169            match token_ref.token_type() {
170                // If we have an ellipsis inside of parentheses, we may also be culling values
171                // Therefore, we don't remove parentheses
172                TokenType::Symbol { symbol } => !matches!(symbol, Symbol::Ellipsis),
173                _ => true,
174            }
175        }
176        // If the internal expression is an if expression, we need to keep the parentheses
177        // as modifying it can lead to issues [e.g. (if <x> then <expr> else <expr>) + 1 is different without parens]
178        #[cfg(feature = "luau")]
179        Expression::IfExpression(_) => false,
180        _ => true,
181    }
182}
183
184/// Formats an Expression node
185pub fn format_expression(ctx: &Context, expression: &Expression, shape: Shape) -> Expression {
186    format_expression_internal(ctx, expression, ExpressionContext::Standard, shape)
187}
188
189/// Internal expression formatter, with access to expression context
190fn format_expression_internal(
191    ctx: &Context,
192    expression: &Expression,
193    context: ExpressionContext,
194    shape: Shape,
195) -> Expression {
196    match expression {
197        Expression::Function(anonymous_function) => {
198            Expression::Function(format_anonymous_function(ctx, anonymous_function, shape))
199        }
200        Expression::FunctionCall(function_call) => {
201            Expression::FunctionCall(format_function_call(ctx, function_call, shape))
202        }
203        #[cfg(feature = "luau")]
204        Expression::IfExpression(if_expression) => {
205            Expression::IfExpression(format_if_expression(ctx, if_expression, shape))
206        }
207        Expression::Number(token_reference) => {
208            Expression::Number(format_token_reference(ctx, token_reference, shape))
209        }
210        Expression::String(token_reference) => {
211            Expression::String(format_token_reference(ctx, token_reference, shape))
212        }
213        #[cfg(feature = "luau")]
214        Expression::InterpolatedString(interpolated_string) => Expression::InterpolatedString(
215            format_interpolated_string(ctx, interpolated_string, shape),
216        ),
217        Expression::Symbol(token_reference) => {
218            Expression::Symbol(format_token_reference(ctx, token_reference, shape))
219        }
220        Expression::TableConstructor(table_constructor) => {
221            Expression::TableConstructor(format_table_constructor(ctx, table_constructor, shape))
222        }
223        Expression::Var(var) => Expression::Var(format_var(ctx, var, shape)),
224
225        #[cfg(feature = "luau")]
226        Expression::TypeAssertion {
227            expression,
228            type_assertion,
229        } => Expression::TypeAssertion {
230            expression: Box::new(format_expression_internal(
231                ctx,
232                expression,
233                ExpressionContext::TypeAssertion,
234                shape,
235            )),
236            type_assertion: format_type_assertion(ctx, type_assertion, shape),
237        },
238        Expression::Parentheses {
239            contained,
240            expression,
241        } => {
242            #[cfg(feature = "luau")]
243            let keep_parentheses = matches!(
244                context,
245                ExpressionContext::Prefix | ExpressionContext::TypeAssertion
246            );
247            #[cfg(not(feature = "luau"))]
248            let keep_parentheses = matches!(context, ExpressionContext::Prefix);
249
250            // Examine whether the internal expression requires parentheses
251            // If not, just format and return the internal expression. Otherwise, format the parentheses
252            let use_internal_expression = check_excess_parentheses(expression, context);
253
254            // If the context is for a prefix, we should always keep the parentheses, as they are always required
255            if use_internal_expression && !keep_parentheses {
256                // Get the leading and trailing comments from contained span and append them onto the expression
257                let (start_parens, end_parens) = contained.tokens();
258                let leading_comments = start_parens
259                    .leading_trivia()
260                    .filter(|token| trivia_util::trivia_is_comment(token))
261                    .flat_map(|x| {
262                        vec![
263                            create_indent_trivia(ctx, shape),
264                            x.to_owned(),
265                            create_newline_trivia(ctx),
266                        ]
267                    })
268                    // .chain(std::iter::once(create_indent_trivia(ctx, shape)))
269                    .collect();
270
271                let trailing_comments = end_parens
272                    .trailing_trivia()
273                    .filter(|token| trivia_util::trivia_is_comment(token))
274                    .flat_map(|x| {
275                        // Prepend a single space beforehand
276                        vec![Token::new(TokenType::spaces(1)), x.to_owned()]
277                    })
278                    .collect();
279
280                format_expression(ctx, expression, shape)
281                    .update_leading_trivia(FormatTriviaType::Append(leading_comments))
282                    .update_trailing_trivia(FormatTriviaType::Append(trailing_comments))
283            } else {
284                Expression::Parentheses {
285                    contained: format_contained_span(ctx, contained, shape),
286                    expression: Box::new(format_expression(ctx, expression, shape + 1)), // 1 = opening parentheses
287                }
288            }
289        }
290        Expression::UnaryOperator { unop, expression } => {
291            let unop = format_unop(ctx, unop, shape);
292            let shape = shape + strip_leading_trivia(&unop).to_string().len();
293            let mut expression = format_expression_internal(
294                ctx,
295                expression,
296                ExpressionContext::UnaryOrBinary,
297                shape,
298            );
299
300            // Special case: if we have `- -foo`, or `-(-foo)` where we have already removed the parentheses, then
301            // it will lead to `--foo`, which is invalid syntax. We must explicitly add/keep the parentheses `-(-foo)`.
302            if let UnOp::Minus(_) = unop {
303                let require_parentheses = match expression {
304                    Expression::UnaryOperator {
305                        unop: UnOp::Minus(_),
306                        ..
307                    } => true,
308                    Expression::Parentheses { ref expression, .. } => matches!(
309                        &**expression,
310                        Expression::UnaryOperator {
311                            unop: UnOp::Minus(_),
312                            ..
313                        }
314                    ),
315                    _ => false,
316                };
317
318                if require_parentheses {
319                    let (new_expression, trailing_comments) =
320                        trivia_util::take_trailing_comments(&expression);
321                    expression = Expression::Parentheses {
322                        contained: ContainedSpan::new(
323                            TokenReference::symbol("(").unwrap(),
324                            TokenReference::symbol(")").unwrap(),
325                        )
326                        .update_trailing_trivia(FormatTriviaType::Append(trailing_comments)),
327                        expression: Box::new(new_expression),
328                    }
329                }
330            }
331
332            Expression::UnaryOperator {
333                unop,
334                expression: Box::new(expression),
335            }
336        }
337        Expression::BinaryOperator { lhs, binop, rhs } => {
338            let context = if let BinOp::Caret(_) = binop {
339                ExpressionContext::BinaryLHSExponent
340            } else {
341                ExpressionContext::BinaryLHS
342            };
343            let lhs = format_expression_internal(ctx, lhs, context, shape);
344            let binop = format_binop(ctx, binop, shape);
345            let shape = shape.take_last_line(&lhs) + binop.to_string().len();
346            Expression::BinaryOperator {
347                lhs: Box::new(lhs),
348                binop,
349                rhs: Box::new(format_expression_internal(
350                    ctx,
351                    rhs,
352                    ExpressionContext::UnaryOrBinary,
353                    shape,
354                )),
355            }
356        }
357        other => panic!("unknown node {:?}", other),
358    }
359}
360
361/// Determines whether the provided [`Expression`] is a brackets string, i.e. `[[string]]`
362/// We care about this because `[ [[string] ]` is invalid syntax if we remove the whitespace
363pub fn is_brackets_string(expression: &Expression) -> bool {
364    match expression {
365        Expression::String(token_reference) => matches!(
366            token_reference.token_type(),
367            TokenType::StringLiteral {
368                quote_type: StringLiteralQuoteType::Brackets,
369                ..
370            }
371        ),
372        #[cfg(feature = "luau")]
373        Expression::TypeAssertion { expression, .. } => is_brackets_string(expression),
374        _ => false,
375    }
376}
377
378pub fn process_dot_name(
379    ctx: &Context,
380    dot: &TokenReference,
381    name: &TokenReference,
382    shape: Shape,
383) -> (TokenReference, TokenReference) {
384    // If there are any comments in between the dot and name,
385    // then taken them out and put them before the dot
386    let (mut dot, mut dot_comments) =
387        take_trailing_comments(&format_token_reference(ctx, dot, shape));
388    let (name, name_comments) = take_leading_comments(&format_token_reference(ctx, name, shape));
389
390    dot_comments.extend(name_comments);
391
392    if !dot_comments.is_empty() {
393        dot = prepend_newline_indent(
394            ctx,
395            &dot.update_leading_trivia(FormatTriviaType::Append(dot_comments)),
396            shape,
397        );
398    }
399
400    (dot, name)
401}
402
403/// Formats an Index Node
404pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
405    match index {
406        Index::Brackets {
407            brackets,
408            expression,
409        } => {
410            if brackets
411                .tokens()
412                .0
413                .has_trailing_comments(CommentSearch::All)
414                || contains_comments(expression)
415                || brackets.tokens().1.has_leading_comments(CommentSearch::All)
416            {
417                let (start_bracket, end_bracket) = brackets.tokens();
418
419                let indent_shape = shape.reset().increment_additional_indent();
420
421                // Format the brackets multiline
422                let brackets = ContainedSpan::new(
423                    fmt_symbol!(ctx, start_bracket, "[", shape).update_trailing_trivia(
424                        FormatTriviaType::Append(vec![
425                            create_newline_trivia(ctx),
426                            create_indent_trivia(ctx, indent_shape),
427                        ]),
428                    ),
429                    format_end_token(ctx, end_bracket, EndTokenType::IndentComments, shape)
430                        .update_leading_trivia(FormatTriviaType::Append(vec![
431                            create_indent_trivia(ctx, shape),
432                        ])),
433                );
434
435                let expression = format_expression(ctx, expression, indent_shape)
436                    .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(
437                        ctx,
438                    )]));
439
440                Index::Brackets {
441                    brackets,
442                    expression,
443                }
444            } else if is_brackets_string(expression) {
445                Index::Brackets {
446                    brackets: format_contained_span(ctx, brackets, shape),
447                    expression: format_expression(ctx, expression, shape + 2) // 2 = "[ "
448                        .update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
449                            TokenType::spaces(1),
450                        )]))
451                        .update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
452                            TokenType::spaces(1),
453                        )])),
454                }
455            } else {
456                Index::Brackets {
457                    brackets: format_contained_span(ctx, brackets, shape),
458                    expression: format_expression(ctx, expression, shape + 1), // 1 = opening bracket
459                }
460            }
461        }
462
463        Index::Dot { dot, name } => {
464            let (dot, name) = process_dot_name(ctx, dot, name, shape);
465            Index::Dot { dot, name }
466        }
467        other => panic!("unknown node {:?}", other),
468    }
469}
470
471// Checks if this is a string (allows strings wrapped in parentheses)
472fn is_string(expression: &Expression) -> bool {
473    match expression {
474        Expression::String(_) => true,
475        #[cfg(feature = "luau")]
476        Expression::InterpolatedString(_) => true,
477        Expression::Parentheses { expression, .. } => is_string(expression),
478        _ => false,
479    }
480}
481
482/// Formats a Prefix Node
483pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
484    match prefix {
485        Prefix::Expression(expression) => {
486            let singleline_format =
487                format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
488            let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
489
490            if singeline_shape.over_budget() && !is_string(expression) {
491                Prefix::Expression(Box::new(format_hanging_expression_(
492                    ctx,
493                    expression,
494                    shape,
495                    ExpressionContext::Prefix,
496                    None,
497                )))
498            } else {
499                Prefix::Expression(Box::new(singleline_format))
500            }
501        }
502        Prefix::Name(token_reference) => {
503            Prefix::Name(format_token_reference(ctx, token_reference, shape))
504        }
505        other => panic!("unknown node {:?}", other),
506    }
507}
508
509/// Formats a Suffix Node
510pub fn format_suffix(
511    ctx: &Context,
512    suffix: &Suffix,
513    shape: Shape,
514    call_next_node: FunctionCallNextNode,
515) -> Suffix {
516    match suffix {
517        Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
518        Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
519        other => panic!("unknown node {:?}", other),
520    }
521}
522
523/// Formats and else if expression onto a single line.
524/// This function does not take into account for comments
525#[cfg(feature = "luau")]
526fn format_else_if_expression_singleline(
527    ctx: &Context,
528    else_if_expression: &ElseIfExpression,
529    shape: Shape,
530) -> ElseIfExpression {
531    let else_if_token = fmt_symbol!(ctx, else_if_expression.else_if_token(), "elseif ", shape);
532    let else_if_condition = remove_condition_parentheses(else_if_expression.condition().to_owned());
533    let else_if_condition = format_expression(ctx, &else_if_condition, shape + 7); // 7 = "elseif "
534    let (then_token, expression) = format_token_expression_sequence(
535        ctx,
536        else_if_expression.then_token(),
537        else_if_expression.expression(),
538        shape.take_first_line(&else_if_condition) + 13, // 13 = "elseif " + " then ",
539    );
540
541    // Add a space before the then token
542    let then_token = then_token.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
543        TokenType::spaces(1),
544    )]));
545
546    ElseIfExpression::new(else_if_condition, expression)
547        .with_else_if_token(else_if_token)
548        .with_then_token(then_token)
549}
550
551/// Formats a `<token> <expr>` sequence, such as `then <expr>` or `else <expr>`.
552/// In particular, this handles when the <expr> has to be formatted onto multiple lines (either due to comments, or going over width)
553#[cfg(feature = "luau")]
554fn format_token_expression_sequence(
555    ctx: &Context,
556    token: &TokenReference,
557    expression: &Expression,
558    shape: Shape,
559) -> (TokenReference, Expression) {
560    const SPACE_LEN: usize = " ".len();
561    let formatted_token = format_token_reference(ctx, token, shape);
562    let token_width = strip_trivia(&formatted_token).to_string().len();
563
564    let formatted_expression =
565        format_expression(ctx, expression, shape.add_width(token_width + SPACE_LEN));
566
567    let requires_multiline_expression = shape.take_first_line(&formatted_expression).over_budget()
568        || token.has_trailing_comments(CommentSearch::All)
569        || trivia_util::contains_comments(
570            expression.update_trailing_trivia(FormatTriviaType::Replace(vec![])),
571        ); // Remove trailing trivia (comments) before checking, as they shouldn't have an impact
572
573    let newline_after_token = token.has_trailing_comments(CommentSearch::Single)
574        || expression.has_leading_comments(CommentSearch::Single);
575
576    let token = match newline_after_token {
577        // `<token>\n`
578        true => formatted_token
579            .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
580        // `<token> `
581        false => {
582            formatted_token.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
583                TokenType::spaces(1),
584            )]))
585        }
586    };
587
588    let expression = match requires_multiline_expression {
589        true => match newline_after_token {
590            true => {
591                let shape = shape.reset().increment_additional_indent();
592                hang_expression(ctx, expression, shape, calculate_hang_level(expression))
593                    .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
594                        ctx, shape,
595                    )]))
596            }
597            false => hang_expression(
598                ctx,
599                expression,
600                shape.add_width(token_width + SPACE_LEN),
601                calculate_hang_level(expression),
602            ),
603        },
604        false => formatted_expression,
605    };
606
607    (token, expression)
608}
609
610/// Formats an [`IfExpression`] node
611#[cfg(feature = "luau")]
612fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
613    // Remove parentheses around the condition
614    let condition = remove_condition_parentheses(if_expression.condition().to_owned());
615    let if_token = fmt_symbol!(ctx, if_expression.if_token(), "if ", shape);
616
617    // Initially format the remainder on a single line
618    let singleline_condition = format_expression(ctx, &condition, shape.with_infinite_width());
619    let then_token = fmt_symbol!(ctx, if_expression.then_token(), " then ", shape);
620    let singleline_expression = format_expression(
621        ctx,
622        if_expression.if_expression(),
623        shape.with_infinite_width(),
624    );
625    let else_ifs = if_expression
626        .else_if_expressions()
627        .map(|else_if_expressions| {
628            else_if_expressions
629                .iter()
630                .map(|else_if_expression| {
631                    format_else_if_expression_singleline(
632                        ctx,
633                        else_if_expression,
634                        shape.with_infinite_width(),
635                    )
636                })
637                .collect::<Vec<_>>()
638        });
639    let else_token = fmt_symbol!(ctx, if_expression.else_token(), " else ", shape);
640    let singleline_else_expression = format_expression(
641        ctx,
642        if_expression.else_expression(),
643        shape.with_infinite_width(),
644    );
645
646    const IF_LENGTH: usize = 3; // "if "
647    const THEN_LENGTH: usize = 6; // " then "
648    const ELSE_LENGTH: usize = 6; // " else "
649
650    // Determine if we need to hang the expression
651    let singleline_shape = (shape + IF_LENGTH + THEN_LENGTH + ELSE_LENGTH)
652        .take_first_line(&strip_trivia(&singleline_condition))
653        .take_first_line(&strip_trivia(&singleline_expression))
654        .take_first_line(&else_ifs.as_ref().map_or(String::new(), |x| {
655            x.iter().map(|x| x.to_string()).collect::<String>()
656        }))
657        .take_first_line(&strip_trivia(&singleline_else_expression));
658
659    let require_multiline_expression = singleline_shape.over_budget()
660        || if_expression
661            .if_token()
662            .has_trailing_comments(CommentSearch::All)
663        || trivia_util::contains_comments(if_expression.condition())
664        || trivia_util::contains_comments(if_expression.then_token())
665        || trivia_util::contains_comments(if_expression.if_expression())
666        || trivia_util::contains_comments(if_expression.else_token())
667        || if_expression
668            .else_if_expressions()
669            .map_or(false, |else_ifs| {
670                else_ifs.iter().any(trivia_util::contains_comments)
671            })
672        || if_expression.else_expression().has_inline_comments()
673        || trivia_util::spans_multiple_lines(&singleline_condition)
674        || trivia_util::spans_multiple_lines(&singleline_expression)
675        || else_ifs.as_ref().map_or(false, |else_ifs| {
676            else_ifs.iter().any(trivia_util::spans_multiple_lines)
677        })
678        || trivia_util::spans_multiple_lines(&singleline_else_expression);
679
680    if require_multiline_expression {
681        let condition = hang_expression_trailing_newline(
682            ctx,
683            if_expression.condition(),
684            shape.increment_additional_indent(),
685            Some(1),
686        );
687        let hanging_shape = shape.reset().increment_additional_indent();
688
689        // then <expr>
690        let (then_token, expression) = format_token_expression_sequence(
691            ctx,
692            if_expression.then_token(),
693            if_expression.if_expression(),
694            hanging_shape,
695        );
696
697        // Indent the then token
698        let then_token =
699            then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
700                ctx,
701                hanging_shape,
702            )]));
703
704        // elseif <condition> then <expr>
705        let else_ifs = if_expression
706            .else_if_expressions()
707            .map(|else_if_expressions| {
708                else_if_expressions
709                    .iter()
710                    .map(|else_if_expression| {
711                        let singleline_else_if = format_else_if_expression_singleline(
712                            ctx,
713                            else_if_expression,
714                            hanging_shape,
715                        );
716                        let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
717
718                        if singleline_shape.over_budget()
719                            || else_if_expression
720                                .else_if_token()
721                                .has_trailing_comments(CommentSearch::All)
722                            || trivia_util::contains_comments(else_if_expression.condition())
723                            || trivia_util::contains_comments(else_if_expression.then_token())
724                        {
725                            let else_if_token = fmt_symbol!(
726                                ctx,
727                                else_if_expression.else_if_token(),
728                                "elseif",
729                                shape
730                            )
731                            .update_leading_trivia(FormatTriviaType::Append(vec![
732                                create_newline_trivia(ctx),
733                                create_indent_trivia(ctx, hanging_shape),
734                            ]));
735
736                            let condiiton_shape =
737                                hanging_shape.reset().increment_additional_indent();
738                            let else_if_condition = hang_expression(
739                                ctx,
740                                &remove_condition_parentheses(
741                                    else_if_expression.condition().to_owned(),
742                                ),
743                                condiiton_shape,
744                                None,
745                            )
746                            .update_leading_trivia(FormatTriviaType::Append(vec![
747                                create_newline_trivia(ctx),
748                                create_indent_trivia(ctx, condiiton_shape),
749                            ]));
750
751                            let hanging_shape =
752                                hanging_shape.take_first_line(&else_if_condition) + 13; // 13 = "elseif " + " then "
753
754                            let (then_token, expression) = format_token_expression_sequence(
755                                ctx,
756                                else_if_expression.then_token(),
757                                else_if_expression.expression(),
758                                hanging_shape,
759                            );
760
761                            let then_token =
762                                then_token.update_leading_trivia(FormatTriviaType::Append(vec![
763                                    create_newline_trivia(ctx),
764                                    create_indent_trivia(ctx, hanging_shape),
765                                ]));
766
767                            ElseIfExpression::new(else_if_condition, expression)
768                                .with_else_if_token(else_if_token)
769                                .with_then_token(then_token)
770                        } else {
771                            singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
772                                vec![
773                                    create_newline_trivia(ctx),
774                                    create_indent_trivia(ctx, hanging_shape),
775                                ],
776                            ))
777                        }
778                    })
779                    .collect::<Vec<_>>()
780            });
781
782        // else <expr>
783        let (else_token, else_expression) = format_token_expression_sequence(
784            ctx,
785            if_expression.else_token(),
786            if_expression.else_expression(),
787            hanging_shape + 5, // 5 = "else "
788        );
789
790        // Put the else on a new line
791        let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
792
793        IfExpression::new(condition, expression, else_expression)
794            .with_if_token(if_token)
795            .with_then_token(then_token)
796            .with_else_if(else_ifs)
797            .with_else_token(else_token)
798    } else {
799        // Prepend a space before each else if
800        let else_ifs = else_ifs.map(|x| {
801            x.iter()
802                .map(|x| {
803                    x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
804                        TokenType::spaces(1),
805                    )]))
806                })
807                .collect()
808        });
809
810        IfExpression::new(
811            singleline_condition,
812            singleline_expression,
813            singleline_else_expression,
814        )
815        .with_if_token(if_token)
816        .with_then_token(then_token)
817        .with_else_if(else_ifs)
818        .with_else_token(else_token)
819    }
820}
821
822#[cfg(feature = "luau")]
823fn format_interpolated_string(
824    ctx: &Context,
825    interpolated_string: &InterpolatedString,
826    shape: Shape,
827) -> InterpolatedString {
828    let mut shape = shape;
829
830    let mut segments = Vec::new();
831    for segment in interpolated_string.segments() {
832        let literal = format_token_reference(ctx, &segment.literal, shape);
833        shape = shape + literal.to_string().len();
834
835        let mut expression = format_expression(ctx, &segment.expression, shape);
836        shape = shape.take_last_line(&expression);
837
838        // If expression is a table constructor, then ensure a space is added beforehand
839        // since `{{` syntax is not permitted
840        if let Expression::TableConstructor { .. } = expression {
841            expression =
842                expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
843                    TokenType::spaces(1),
844                )]))
845        }
846
847        segments.push(InterpolatedStringSegment {
848            literal,
849            expression,
850        })
851    }
852
853    interpolated_string
854        .to_owned()
855        .with_segments(segments)
856        .with_last_string(format_token_reference(
857            ctx,
858            interpolated_string.last_string(),
859            shape,
860        ))
861}
862
863/// Formats a Var Node
864pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
865    match var {
866        Var::Name(token_reference) => {
867            Var::Name(format_token_reference(ctx, token_reference, shape))
868        }
869        Var::Expression(var_expression) => {
870            Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
871        }
872        other => panic!("unknown node {:?}", other),
873    }
874}
875
876pub fn format_var_expression(
877    ctx: &Context,
878    var_expression: &VarExpression,
879    shape: Shape,
880) -> VarExpression {
881    // A VarExpression is pretty much exactly the same as FunctionCall, so we repurpose
882    // format_function_call for that, and reuse its output
883    let function_call = format_function_call(
884        ctx,
885        &FunctionCall::new(var_expression.prefix().clone())
886            .with_suffixes(var_expression.suffixes().cloned().collect()),
887        shape,
888    );
889    VarExpression::new(function_call.prefix().clone())
890        .with_suffixes(function_call.suffixes().cloned().collect())
891}
892
893/// Formats an UnOp Node
894pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
895    fmt_op!(ctx, UnOp, unop, shape, {
896        Minus = "-",
897        Not = "not ",
898        Hash = "#",
899        #[cfg(feature = "lua53")]
900        Tilde = "~",
901    }, |other| panic!("unknown node {:?}", other))
902}
903
904/// Pushes a [`BinOp`] onto a newline, and indent its depending on indent_level.
905/// Preserves any leading comments, and moves trailing comments to before the BinOp.
906/// Also takes in the [`Expression`] present on the RHS of the BinOp - this is needed so that we can take any
907/// leading comments from the expression, and place them before the BinOp.
908fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
909    // Get the leading comments of a binop, as we need to preserve them
910    // Intersperse a newline and indent trivia between them
911    // iter_intersperse is currently not available, so we need to do something different. Tracking issue: https://github.com/rust-lang/rust/issues/79524
912    let mut leading_comments = binop
913        .leading_comments()
914        .iter()
915        .flat_map(|x| {
916            vec![
917                create_newline_trivia(ctx),
918                create_indent_trivia(ctx, shape),
919                x.to_owned(),
920            ]
921        })
922        .collect::<Vec<_>>();
923
924    // If there are any comments trailing the BinOp, we need to move them to before the BinOp
925    let mut trailing_comments = binop.trailing_comments();
926    leading_comments.append(&mut trailing_comments);
927
928    // If there are any leading comments to the RHS expression, we need to move them to before the BinOp
929    let mut expression_leading_comments = rhs
930        .leading_comments()
931        .iter()
932        .flat_map(|x| {
933            vec![
934                create_newline_trivia(ctx),
935                create_indent_trivia(ctx, shape),
936                x.to_owned(),
937            ]
938        })
939        .collect::<Vec<_>>();
940    leading_comments.append(&mut expression_leading_comments);
941
942    // Create a newline just before the BinOp, and preserve the indentation
943    leading_comments.push(create_newline_trivia(ctx));
944    leading_comments.push(create_indent_trivia(ctx, shape));
945
946    binop.update_trivia(
947        FormatTriviaType::Replace(leading_comments),
948        FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
949    )
950}
951
952/// Finds the length of the expression which matches the precedence level of the provided binop
953fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
954    match expression {
955        Expression::BinaryOperator { lhs, binop, rhs } => {
956            if binop.precedence() >= top_binop.precedence()
957                && binop.is_right_associative() == top_binop.is_right_associative()
958            {
959                if binop.is_right_associative() {
960                    binop_expression_length(rhs, top_binop)
961                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
962                        + strip_trivia(&**lhs).to_string().len()
963                } else {
964                    binop_expression_length(lhs, top_binop)
965                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
966                        + strip_trivia(&**rhs).to_string().len()
967                }
968            } else {
969                0
970            }
971        }
972        _ => strip_trivia(expression).to_string().len(),
973    }
974}
975
976fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
977    match expression {
978        Expression::BinaryOperator { lhs, binop, rhs } => {
979            if binop.precedence() == top_binop.precedence() {
980                contains_comments(binop)
981                    || rhs.has_leading_comments(CommentSearch::All)
982                    || lhs.has_trailing_comments(CommentSearch::All)
983                    || binop_expression_contains_comments(lhs, top_binop)
984                    || binop_expression_contains_comments(rhs, top_binop)
985            } else {
986                false
987            }
988        }
989        _ => false,
990    }
991}
992
993/// Converts an item to a range
994trait ToRange {
995    fn to_range(&self) -> (usize, usize);
996}
997
998impl ToRange for (usize, usize) {
999    fn to_range(&self) -> (usize, usize) {
1000        *self
1001    }
1002}
1003
1004impl ToRange for Expression {
1005    fn to_range(&self) -> (usize, usize) {
1006        let (start, end) = self.range().unwrap();
1007        (start.bytes(), end.bytes())
1008    }
1009}
1010
1011/// This struct encompasses information about the leftmost-expression in a BinaryExpression tree.
1012/// It holds the range of the leftmost binary expression, and the original additional indent level of this range.
1013/// This struct is only used when the hanging binary expression involves a hang level, for example:
1014/// ```lua
1015/// foo
1016///    + bar
1017///    + baz
1018/// ```
1019/// or in a larger context:
1020/// ```lua
1021/// local someVariable = foo
1022///    + bar
1023///    + baz
1024/// ```
1025/// As seen, the first item (`foo`) is inlined, and has an indent level one lower than the rest of the binary
1026/// expressions. We want to ensure that whenever we have `foo` in our expression, we use the original indentation level
1027/// because the expression is (at this current point in time) inlined - otherwise, it will be over-indented.
1028/// We hold the original indentation level in case we are deep down in the recursive calls:
1029/// ```lua
1030/// local ratio = (minAxis - minAxisSize) / delta * (self.props.maxScaleRatio - self.props.minScaleRatio)
1031///     + self.props.minScaleRatio
1032/// ```
1033/// Since the first line contains binary operators at a different precedence level to the `+`, then the indentation
1034/// level has been increased even further. But we want to use the original indentation level, because as it stands,
1035/// the expression is currently inlined on the original line.
1036#[derive(Clone, Copy, Debug)]
1037struct LeftmostRangeHang {
1038    range: (usize, usize),
1039    original_additional_indent_level: usize,
1040}
1041
1042impl LeftmostRangeHang {
1043    /// Finds the leftmost expression from the given (full) expression, and then creates a [`LeftmostRangeHang`]
1044    /// to represent it
1045    fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1046        match expression {
1047            Expression::BinaryOperator { lhs, .. } => {
1048                Self::find(lhs, original_additional_indent_level)
1049            }
1050            _ => Self {
1051                range: expression.to_range(),
1052                original_additional_indent_level,
1053            },
1054        }
1055    }
1056
1057    /// Given an [`Expression`], returns the [`Shape`] to use for this expression.
1058    /// This function checks the provided expression to see if the LeftmostRange falls inside of it.
1059    /// If so, then we need to use the original indentation level shape, as (so far) the expression is inlined.
1060    fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1061        let (expression_start, expression_end) = item.to_range();
1062        let (lhs_start, lhs_end) = self.range;
1063
1064        if lhs_start >= expression_start && lhs_end <= expression_end {
1065            shape.with_indent(
1066                shape
1067                    .indent()
1068                    .with_additional_indent(self.original_additional_indent_level),
1069            )
1070        } else {
1071            shape
1072        }
1073    }
1074}
1075
1076fn is_hang_binop_over_width(
1077    shape: Shape,
1078    expression: &Expression,
1079    top_binop: &BinOp,
1080    lhs_range: Option<LeftmostRangeHang>,
1081) -> bool {
1082    let shape = if let Some(lhs_hang) = lhs_range {
1083        lhs_hang.required_shape(shape, expression)
1084    } else {
1085        shape
1086    };
1087
1088    shape
1089        .add_width(binop_expression_length(expression, top_binop))
1090        .over_budget()
1091}
1092
1093/// If present, finds the precedence level of the provided binop in the BinOp expression. Otherwise, returns 0
1094fn binop_precedence_level(expression: &Expression) -> u8 {
1095    match expression {
1096        Expression::BinaryOperator { binop, .. } => binop.precedence(),
1097        _ => 0,
1098    }
1099}
1100
1101fn did_hang_expression(expression: &Expression) -> bool {
1102    if let Expression::BinaryOperator { binop, .. } = expression {
1103        // Examine the binop's leading trivia for a newline
1104        // TODO: this works..., but is it the right solution?
1105        binop
1106            .surrounding_trivia()
1107            .0
1108            .iter()
1109            .any(|x| trivia_is_newline(x))
1110    } else {
1111        false
1112    }
1113}
1114
1115#[derive(Debug)]
1116enum ExpressionSide {
1117    Left,
1118    Right,
1119}
1120
1121fn hang_binop_expression(
1122    ctx: &Context,
1123    expression: Expression,
1124    top_binop: BinOp,
1125    shape: Shape,
1126    lhs_range: Option<LeftmostRangeHang>,
1127) -> Expression {
1128    const SPACE_LEN: usize = " ".len();
1129
1130    let full_expression = expression.to_owned();
1131
1132    match expression {
1133        Expression::BinaryOperator { lhs, binop, rhs } => {
1134            // Keep grouping together all operators with the same precedence level as the main BinOp
1135            // They should also have the same associativity
1136            let same_op_level = binop.precedence() == top_binop.precedence()
1137                && binop.is_right_associative() == top_binop.is_right_associative();
1138            let is_right_associative = binop.is_right_associative();
1139
1140            let test_shape = if same_op_level {
1141                shape
1142            } else {
1143                shape.increment_additional_indent()
1144            };
1145
1146            let side_to_hang = if is_right_associative {
1147                ExpressionSide::Right
1148            } else {
1149                ExpressionSide::Left
1150            };
1151
1152            // TODO/FIXME: using test_shape here leads to too high of an indent level, causing the expression to hang unnecessarily
1153            let over_column_width =
1154                is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
1155            let should_hang = same_op_level
1156                || over_column_width
1157                || binop_expression_contains_comments(&full_expression, &binop);
1158
1159            // Only use the indented shape if we are planning to hang
1160            let shape = if should_hang { test_shape } else { shape };
1161
1162            let mut new_binop = format_binop(ctx, &binop, shape);
1163            if should_hang {
1164                new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
1165            }
1166
1167            let (lhs, rhs) = match should_hang {
1168                true => {
1169                    let lhs_shape = shape;
1170                    let rhs_shape =
1171                        shape.reset() + strip_trivia(&new_binop).to_string().len() + SPACE_LEN;
1172
1173                    let (lhs, rhs) = match side_to_hang {
1174                        ExpressionSide::Left => (
1175                            hang_binop_expression(
1176                                ctx,
1177                                *lhs,
1178                                if same_op_level {
1179                                    top_binop
1180                                } else {
1181                                    binop.clone()
1182                                },
1183                                lhs_shape,
1184                                lhs_range,
1185                            ),
1186                            if contains_comments(&*rhs) {
1187                                hang_binop_expression(ctx, *rhs, binop, shape, lhs_range)
1188                            } else {
1189                                format_expression_internal(
1190                                    ctx,
1191                                    &rhs,
1192                                    ExpressionContext::UnaryOrBinary,
1193                                    rhs_shape,
1194                                )
1195                            },
1196                        ),
1197                        ExpressionSide::Right => (
1198                            if contains_comments(&*lhs) {
1199                                hang_binop_expression(ctx, *lhs, binop.clone(), shape, lhs_range)
1200                            } else {
1201                                let context = if let BinOp::Caret(_) = binop {
1202                                    ExpressionContext::BinaryLHSExponent
1203                                } else {
1204                                    ExpressionContext::BinaryLHS
1205                                };
1206                                format_expression_internal(ctx, &lhs, context, lhs_shape)
1207                            },
1208                            hang_binop_expression(
1209                                ctx,
1210                                *rhs,
1211                                if same_op_level { top_binop } else { binop },
1212                                rhs_shape,
1213                                lhs_range,
1214                            ),
1215                        ),
1216                    };
1217                    (
1218                        lhs,
1219                        rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1220                    )
1221                }
1222                false => {
1223                    // Check if the chain still has comments deeper inside of it.
1224                    // If it does, we need to hang that part of the chain still, otherwise the comments will mess it up
1225                    let lhs = if contains_comments(&*lhs) {
1226                        hang_binop_expression(ctx, *lhs, binop.to_owned(), shape, lhs_range)
1227                    } else {
1228                        let context = if let BinOp::Caret(_) = binop {
1229                            ExpressionContext::BinaryLHSExponent
1230                        } else {
1231                            ExpressionContext::BinaryLHS
1232                        };
1233                        format_expression_internal(ctx, &lhs, context, shape)
1234                    };
1235
1236                    let rhs = if contains_comments(&*rhs) {
1237                        hang_binop_expression(ctx, *rhs, binop, shape, lhs_range)
1238                    } else {
1239                        format_expression_internal(
1240                            ctx,
1241                            &rhs,
1242                            ExpressionContext::UnaryOrBinary,
1243                            shape,
1244                        )
1245                    };
1246
1247                    (lhs, rhs)
1248                }
1249            };
1250
1251            Expression::BinaryOperator {
1252                lhs: Box::new(lhs),
1253                binop: new_binop,
1254                rhs: Box::new(rhs),
1255            }
1256        }
1257        // Base case: no more binary operators - just return to normal splitting
1258        _ => format_hanging_expression_(
1259            ctx,
1260            &expression,
1261            shape,
1262            ExpressionContext::Standard,
1263            lhs_range,
1264        ),
1265    }
1266}
1267
1268/// Internal expression formatter, where the binop is also hung
1269fn format_hanging_expression_(
1270    ctx: &Context,
1271    expression: &Expression,
1272    shape: Shape,
1273    expression_context: ExpressionContext,
1274    lhs_range: Option<LeftmostRangeHang>,
1275) -> Expression {
1276    let expression_range = expression.to_range();
1277
1278    match expression {
1279        #[cfg(feature = "luau")]
1280        Expression::TypeAssertion {
1281            expression,
1282            type_assertion,
1283        } => {
1284            // If we have a type assertion, we increment the current shape with the size of the assertion
1285            // to "force" the parentheses to hang if necessary
1286            let (expression_context, value_shape) = (
1287                ExpressionContext::TypeAssertion,
1288                shape.take_first_line(&strip_trivia(type_assertion)),
1289            );
1290
1291            let expression = format_hanging_expression_(
1292                ctx,
1293                expression,
1294                value_shape,
1295                expression_context,
1296                lhs_range,
1297            );
1298
1299            // Update the shape used to format the type assertion
1300            #[cfg(feature = "luau")]
1301            let assertion_shape = shape.take_last_line(&expression);
1302
1303            Expression::TypeAssertion {
1304                expression: Box::new(expression),
1305                type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1306            }
1307        }
1308        Expression::Parentheses {
1309            contained,
1310            expression,
1311        } => {
1312            let lhs_shape = if let Some(lhs_hang) = lhs_range {
1313                lhs_hang.required_shape(shape, &expression_range)
1314            } else {
1315                shape
1316            };
1317            #[cfg(feature = "luau")]
1318            let keep_parentheses = matches!(
1319                expression_context,
1320                ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1321            );
1322            #[cfg(not(feature = "luau"))]
1323            let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1324
1325            // Examine whether the internal expression requires parentheses
1326            // If not, just format and return the internal expression. Otherwise, format the parentheses
1327            let use_internal_expression = check_excess_parentheses(expression, expression_context);
1328
1329            // If the context is for a prefix, we should always keep the parentheses, as they are always required
1330            if use_internal_expression && !keep_parentheses {
1331                format_hanging_expression_(
1332                    ctx,
1333                    expression,
1334                    lhs_shape,
1335                    expression_context,
1336                    lhs_range,
1337                )
1338            } else {
1339                let contained = format_contained_span(ctx, contained, lhs_shape);
1340
1341                // Provide a sample formatting to see how large it is
1342                // Examine the expression itself to see if needs to be split onto multiple lines
1343                let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); // 1 = opening parentheses
1344
1345                let expression_str = formatted_expression.to_string();
1346                if !contains_comments(expression)
1347                    && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1348                {
1349                    // The expression inside the parentheses is small, we do not need to break it down further
1350                    return Expression::Parentheses {
1351                        contained,
1352                        expression: Box::new(formatted_expression),
1353                    };
1354                }
1355
1356                // Update the expression shape to be used inside the parentheses, applying the indent increase
1357                let expression_shape = lhs_shape.reset().increment_additional_indent();
1358
1359                // Modify the parentheses to hang the expression
1360                let (start_token, end_token) = contained.tokens();
1361
1362                // Create a newline after the start brace and before the end brace
1363                // Also, indent enough for the first expression in the start brace
1364                let contained = ContainedSpan::new(
1365                    start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1366                        create_newline_trivia(ctx),
1367                        create_indent_trivia(ctx, expression_shape),
1368                    ])),
1369                    end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1370                        create_newline_trivia(ctx),
1371                        create_indent_trivia(ctx, lhs_shape),
1372                    ])),
1373                );
1374
1375                Expression::Parentheses {
1376                    contained,
1377                    expression: Box::new(format_hanging_expression_(
1378                        ctx,
1379                        expression,
1380                        expression_shape,
1381                        ExpressionContext::Standard,
1382                        None,
1383                    )),
1384                }
1385            }
1386        }
1387        Expression::UnaryOperator { unop, expression } => {
1388            let unop = format_unop(ctx, unop, shape);
1389            let shape = shape + strip_leading_trivia(&unop).to_string().len();
1390            let expression = format_hanging_expression_(
1391                ctx,
1392                expression,
1393                shape,
1394                ExpressionContext::UnaryOrBinary,
1395                lhs_range,
1396            );
1397
1398            Expression::UnaryOperator {
1399                unop,
1400                expression: Box::new(expression),
1401            }
1402        }
1403        Expression::BinaryOperator { lhs, binop, rhs } => {
1404            // Don't format the lhs and rhs here, because it will be handled later when hang_binop_expression calls back for a Value
1405            let lhs =
1406                hang_binop_expression(ctx, *lhs.to_owned(), binop.to_owned(), shape, lhs_range);
1407
1408            let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop
1409            let mut new_binop = format_binop(ctx, binop, current_shape);
1410
1411            let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; // 1 = space after binop
1412
1413            let mut new_rhs = hang_binop_expression(
1414                ctx,
1415                *rhs.to_owned(),
1416                binop.to_owned(),
1417                singleline_shape,
1418                None,
1419            );
1420
1421            // Examine the last line to see if we need to hang this binop, or if the precedence levels match
1422            if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1423                || (did_hang_expression(&new_rhs)
1424                    && binop_precedence_level(&new_rhs) >= binop.precedence())
1425                || contains_comments(binop)
1426                || lhs.has_trailing_comments(CommentSearch::All)
1427                || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1428            {
1429                let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1430                new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1431                new_rhs = hang_binop_expression(
1432                    ctx,
1433                    *rhs.to_owned(),
1434                    binop.to_owned(),
1435                    hanging_shape,
1436                    None,
1437                )
1438                .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1439            }
1440
1441            Expression::BinaryOperator {
1442                lhs: Box::new(lhs),
1443                binop: new_binop,
1444                rhs: Box::new(new_rhs),
1445            }
1446        }
1447        _ => {
1448            let value_shape = if let Some(lhs_hang) = lhs_range {
1449                lhs_hang.required_shape(shape, &expression_range)
1450            } else {
1451                shape
1452            };
1453
1454            format_expression_internal(ctx, expression, expression_context, value_shape)
1455        }
1456    }
1457}
1458
1459pub fn hang_expression(
1460    ctx: &Context,
1461    expression: &Expression,
1462    shape: Shape,
1463    hang_level: Option<usize>,
1464) -> Expression {
1465    let original_additional_indent_level = shape.indent().additional_indent();
1466    let shape = match hang_level {
1467        Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1468        None => shape,
1469    };
1470
1471    let lhs_range =
1472        hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1473
1474    format_hanging_expression_(
1475        ctx,
1476        expression,
1477        shape,
1478        ExpressionContext::Standard,
1479        lhs_range,
1480    )
1481}
1482
1483pub fn hang_expression_trailing_newline(
1484    ctx: &Context,
1485    expression: &Expression,
1486    shape: Shape,
1487    hang_level: Option<usize>,
1488) -> Expression {
1489    hang_expression(ctx, expression, shape, hang_level)
1490        .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1491}