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            .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::contains_comments))
670        || if_expression.else_expression().has_inline_comments()
671        || trivia_util::spans_multiple_lines(&singleline_condition)
672        || trivia_util::spans_multiple_lines(&singleline_expression)
673        || else_ifs
674            .as_ref()
675            .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::spans_multiple_lines))
676        || trivia_util::spans_multiple_lines(&singleline_else_expression);
677
678    if require_multiline_expression {
679        let condition = hang_expression_trailing_newline(
680            ctx,
681            if_expression.condition(),
682            shape.increment_additional_indent(),
683            Some(1),
684        );
685        let hanging_shape = shape.reset().increment_additional_indent();
686
687        // then <expr>
688        let (then_token, expression) = format_token_expression_sequence(
689            ctx,
690            if_expression.then_token(),
691            if_expression.if_expression(),
692            hanging_shape,
693        );
694
695        // Indent the then token
696        let then_token =
697            then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
698                ctx,
699                hanging_shape,
700            )]));
701
702        // elseif <condition> then <expr>
703        let else_ifs = if_expression
704            .else_if_expressions()
705            .map(|else_if_expressions| {
706                else_if_expressions
707                    .iter()
708                    .map(|else_if_expression| {
709                        let singleline_else_if = format_else_if_expression_singleline(
710                            ctx,
711                            else_if_expression,
712                            hanging_shape,
713                        );
714                        let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
715
716                        if singleline_shape.over_budget()
717                            || else_if_expression
718                                .else_if_token()
719                                .has_trailing_comments(CommentSearch::All)
720                            || trivia_util::contains_comments(else_if_expression.condition())
721                            || trivia_util::contains_comments(else_if_expression.then_token())
722                        {
723                            let else_if_token = fmt_symbol!(
724                                ctx,
725                                else_if_expression.else_if_token(),
726                                "elseif",
727                                shape
728                            )
729                            .update_leading_trivia(FormatTriviaType::Append(vec![
730                                create_newline_trivia(ctx),
731                                create_indent_trivia(ctx, hanging_shape),
732                            ]));
733
734                            let condiiton_shape =
735                                hanging_shape.reset().increment_additional_indent();
736                            let else_if_condition = hang_expression(
737                                ctx,
738                                &remove_condition_parentheses(
739                                    else_if_expression.condition().to_owned(),
740                                ),
741                                condiiton_shape,
742                                None,
743                            )
744                            .update_leading_trivia(FormatTriviaType::Append(vec![
745                                create_newline_trivia(ctx),
746                                create_indent_trivia(ctx, condiiton_shape),
747                            ]));
748
749                            let hanging_shape =
750                                hanging_shape.take_first_line(&else_if_condition) + 13; // 13 = "elseif " + " then "
751
752                            let (then_token, expression) = format_token_expression_sequence(
753                                ctx,
754                                else_if_expression.then_token(),
755                                else_if_expression.expression(),
756                                hanging_shape,
757                            );
758
759                            let then_token =
760                                then_token.update_leading_trivia(FormatTriviaType::Append(vec![
761                                    create_newline_trivia(ctx),
762                                    create_indent_trivia(ctx, hanging_shape),
763                                ]));
764
765                            ElseIfExpression::new(else_if_condition, expression)
766                                .with_else_if_token(else_if_token)
767                                .with_then_token(then_token)
768                        } else {
769                            singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
770                                vec![
771                                    create_newline_trivia(ctx),
772                                    create_indent_trivia(ctx, hanging_shape),
773                                ],
774                            ))
775                        }
776                    })
777                    .collect::<Vec<_>>()
778            });
779
780        // else <expr>
781        let (else_token, else_expression) = format_token_expression_sequence(
782            ctx,
783            if_expression.else_token(),
784            if_expression.else_expression(),
785            hanging_shape + 5, // 5 = "else "
786        );
787
788        // Put the else on a new line
789        let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
790
791        IfExpression::new(condition, expression, else_expression)
792            .with_if_token(if_token)
793            .with_then_token(then_token)
794            .with_else_if(else_ifs)
795            .with_else_token(else_token)
796    } else {
797        // Prepend a space before each else if
798        let else_ifs = else_ifs.map(|x| {
799            x.iter()
800                .map(|x| {
801                    x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
802                        TokenType::spaces(1),
803                    )]))
804                })
805                .collect()
806        });
807
808        IfExpression::new(
809            singleline_condition,
810            singleline_expression,
811            singleline_else_expression,
812        )
813        .with_if_token(if_token)
814        .with_then_token(then_token)
815        .with_else_if(else_ifs)
816        .with_else_token(else_token)
817    }
818}
819
820#[cfg(feature = "luau")]
821fn format_interpolated_string(
822    ctx: &Context,
823    interpolated_string: &InterpolatedString,
824    shape: Shape,
825) -> InterpolatedString {
826    let mut shape = shape;
827
828    let mut segments = Vec::new();
829    for segment in interpolated_string.segments() {
830        let literal = format_token_reference(ctx, &segment.literal, shape);
831        shape = shape + literal.to_string().len();
832
833        let mut expression = format_expression(ctx, &segment.expression, shape);
834        shape = shape.take_last_line(&expression);
835
836        // If expression is a table constructor, then ensure a space is added beforehand
837        // since `{{` syntax is not permitted
838        if let Expression::TableConstructor { .. } = expression {
839            expression =
840                expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
841                    TokenType::spaces(1),
842                )]))
843        }
844
845        segments.push(InterpolatedStringSegment {
846            literal,
847            expression,
848        })
849    }
850
851    interpolated_string
852        .to_owned()
853        .with_segments(segments)
854        .with_last_string(format_token_reference(
855            ctx,
856            interpolated_string.last_string(),
857            shape,
858        ))
859}
860
861/// Formats a Var Node
862pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
863    match var {
864        Var::Name(token_reference) => {
865            Var::Name(format_token_reference(ctx, token_reference, shape))
866        }
867        Var::Expression(var_expression) => {
868            Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
869        }
870        other => panic!("unknown node {:?}", other),
871    }
872}
873
874pub fn format_var_expression(
875    ctx: &Context,
876    var_expression: &VarExpression,
877    shape: Shape,
878) -> VarExpression {
879    // A VarExpression is pretty much exactly the same as FunctionCall, so we repurpose
880    // format_function_call for that, and reuse its output
881    let function_call = format_function_call(
882        ctx,
883        &FunctionCall::new(var_expression.prefix().clone())
884            .with_suffixes(var_expression.suffixes().cloned().collect()),
885        shape,
886    );
887    VarExpression::new(function_call.prefix().clone())
888        .with_suffixes(function_call.suffixes().cloned().collect())
889}
890
891/// Formats an UnOp Node
892pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
893    fmt_op!(ctx, UnOp, unop, shape, {
894        Minus = "-",
895        Not = "not ",
896        Hash = "#",
897        #[cfg(feature = "lua53")]
898        Tilde = "~",
899    }, |other| panic!("unknown node {:?}", other))
900}
901
902/// Pushes a [`BinOp`] onto a newline, and indent its depending on indent_level.
903/// Preserves any leading comments, and moves trailing comments to before the BinOp.
904/// Also takes in the [`Expression`] present on the RHS of the BinOp - this is needed so that we can take any
905/// leading comments from the expression, and place them before the BinOp.
906fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
907    // Get the leading comments of a binop, as we need to preserve them
908    // Intersperse a newline and indent trivia between them
909    // iter_intersperse is currently not available, so we need to do something different. Tracking issue: https://github.com/rust-lang/rust/issues/79524
910    let mut leading_comments = binop
911        .leading_comments()
912        .iter()
913        .flat_map(|x| {
914            vec![
915                create_newline_trivia(ctx),
916                create_indent_trivia(ctx, shape),
917                x.to_owned(),
918            ]
919        })
920        .collect::<Vec<_>>();
921
922    // If there are any comments trailing the BinOp, we need to move them to before the BinOp
923    let mut trailing_comments = binop.trailing_comments();
924    leading_comments.append(&mut trailing_comments);
925
926    // If there are any leading comments to the RHS expression, we need to move them to before the BinOp
927    let mut expression_leading_comments = rhs
928        .leading_comments()
929        .iter()
930        .flat_map(|x| {
931            vec![
932                create_newline_trivia(ctx),
933                create_indent_trivia(ctx, shape),
934                x.to_owned(),
935            ]
936        })
937        .collect::<Vec<_>>();
938    leading_comments.append(&mut expression_leading_comments);
939
940    // Create a newline just before the BinOp, and preserve the indentation
941    leading_comments.push(create_newline_trivia(ctx));
942    leading_comments.push(create_indent_trivia(ctx, shape));
943
944    binop.update_trivia(
945        FormatTriviaType::Replace(leading_comments),
946        FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
947    )
948}
949
950/// Finds the length of the expression which matches the precedence level of the provided binop
951fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
952    match expression {
953        Expression::BinaryOperator { lhs, binop, rhs } => {
954            if binop.precedence() >= top_binop.precedence()
955                && binop.is_right_associative() == top_binop.is_right_associative()
956            {
957                if binop.is_right_associative() {
958                    binop_expression_length(rhs, top_binop)
959                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
960                        + strip_trivia(&**lhs).to_string().len()
961                } else {
962                    binop_expression_length(lhs, top_binop)
963                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
964                        + strip_trivia(&**rhs).to_string().len()
965                }
966            } else {
967                0
968            }
969        }
970        _ => strip_trivia(expression).to_string().len(),
971    }
972}
973
974fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
975    match expression {
976        Expression::BinaryOperator { lhs, binop, rhs } => {
977            if binop.precedence() == top_binop.precedence() {
978                contains_comments(binop)
979                    || rhs.has_leading_comments(CommentSearch::All)
980                    || lhs.has_trailing_comments(CommentSearch::All)
981                    || binop_expression_contains_comments(lhs, top_binop)
982                    || binop_expression_contains_comments(rhs, top_binop)
983            } else {
984                false
985            }
986        }
987        _ => false,
988    }
989}
990
991/// Converts an item to a range
992trait ToRange {
993    fn to_range(&self) -> (usize, usize);
994}
995
996impl ToRange for (usize, usize) {
997    fn to_range(&self) -> (usize, usize) {
998        *self
999    }
1000}
1001
1002impl ToRange for Expression {
1003    fn to_range(&self) -> (usize, usize) {
1004        let (start, end) = self.range().unwrap();
1005        (start.bytes(), end.bytes())
1006    }
1007}
1008
1009/// This struct encompasses information about the leftmost-expression in a BinaryExpression tree.
1010/// It holds the range of the leftmost binary expression, and the original additional indent level of this range.
1011/// This struct is only used when the hanging binary expression involves a hang level, for example:
1012/// ```lua
1013/// foo
1014///    + bar
1015///    + baz
1016/// ```
1017/// or in a larger context:
1018/// ```lua
1019/// local someVariable = foo
1020///    + bar
1021///    + baz
1022/// ```
1023/// As seen, the first item (`foo`) is inlined, and has an indent level one lower than the rest of the binary
1024/// expressions. We want to ensure that whenever we have `foo` in our expression, we use the original indentation level
1025/// because the expression is (at this current point in time) inlined - otherwise, it will be over-indented.
1026/// We hold the original indentation level in case we are deep down in the recursive calls:
1027/// ```lua
1028/// local ratio = (minAxis - minAxisSize) / delta * (self.props.maxScaleRatio - self.props.minScaleRatio)
1029///     + self.props.minScaleRatio
1030/// ```
1031/// Since the first line contains binary operators at a different precedence level to the `+`, then the indentation
1032/// level has been increased even further. But we want to use the original indentation level, because as it stands,
1033/// the expression is currently inlined on the original line.
1034#[derive(Clone, Copy, Debug)]
1035struct LeftmostRangeHang {
1036    range: (usize, usize),
1037    original_additional_indent_level: usize,
1038}
1039
1040impl LeftmostRangeHang {
1041    /// Finds the leftmost expression from the given (full) expression, and then creates a [`LeftmostRangeHang`]
1042    /// to represent it
1043    fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1044        match expression {
1045            Expression::BinaryOperator { lhs, .. } => {
1046                Self::find(lhs, original_additional_indent_level)
1047            }
1048            _ => Self {
1049                range: expression.to_range(),
1050                original_additional_indent_level,
1051            },
1052        }
1053    }
1054
1055    /// Given an [`Expression`], returns the [`Shape`] to use for this expression.
1056    /// This function checks the provided expression to see if the LeftmostRange falls inside of it.
1057    /// If so, then we need to use the original indentation level shape, as (so far) the expression is inlined.
1058    fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1059        let (expression_start, expression_end) = item.to_range();
1060        let (lhs_start, lhs_end) = self.range;
1061
1062        if lhs_start >= expression_start && lhs_end <= expression_end {
1063            shape.with_indent(
1064                shape
1065                    .indent()
1066                    .with_additional_indent(self.original_additional_indent_level),
1067            )
1068        } else {
1069            shape
1070        }
1071    }
1072}
1073
1074fn is_hang_binop_over_width(
1075    shape: Shape,
1076    expression: &Expression,
1077    top_binop: &BinOp,
1078    lhs_range: Option<LeftmostRangeHang>,
1079) -> bool {
1080    let shape = if let Some(lhs_hang) = lhs_range {
1081        lhs_hang.required_shape(shape, expression)
1082    } else {
1083        shape
1084    };
1085
1086    shape
1087        .add_width(binop_expression_length(expression, top_binop))
1088        .over_budget()
1089}
1090
1091/// If present, finds the precedence level of the provided binop in the BinOp expression. Otherwise, returns 0
1092fn binop_precedence_level(expression: &Expression) -> u8 {
1093    match expression {
1094        Expression::BinaryOperator { binop, .. } => binop.precedence(),
1095        _ => 0,
1096    }
1097}
1098
1099fn did_hang_expression(expression: &Expression) -> bool {
1100    if let Expression::BinaryOperator { binop, .. } = expression {
1101        // Examine the binop's leading trivia for a newline
1102        // TODO: this works..., but is it the right solution?
1103        binop
1104            .surrounding_trivia()
1105            .0
1106            .iter()
1107            .any(|x| trivia_is_newline(x))
1108    } else {
1109        false
1110    }
1111}
1112
1113#[derive(Debug)]
1114enum ExpressionSide {
1115    Left,
1116    Right,
1117}
1118
1119fn hang_binop_expression(
1120    ctx: &Context,
1121    expression: Expression,
1122    top_binop: BinOp,
1123    shape: Shape,
1124    lhs_range: Option<LeftmostRangeHang>,
1125    expression_context: ExpressionContext,
1126) -> Expression {
1127    const SPACE_LEN: usize = " ".len();
1128
1129    let full_expression = expression.to_owned();
1130
1131    match expression {
1132        Expression::BinaryOperator { lhs, binop, rhs } => {
1133            // Keep grouping together all operators with the same precedence level as the main BinOp
1134            // They should also have the same associativity
1135            let same_op_level = binop.precedence() == top_binop.precedence()
1136                && binop.is_right_associative() == top_binop.is_right_associative();
1137            let is_right_associative = binop.is_right_associative();
1138
1139            let test_shape = if same_op_level {
1140                shape
1141            } else {
1142                shape.increment_additional_indent()
1143            };
1144
1145            let side_to_hang = if is_right_associative {
1146                ExpressionSide::Right
1147            } else {
1148                ExpressionSide::Left
1149            };
1150
1151            // TODO/FIXME: using test_shape here leads to too high of an indent level, causing the expression to hang unnecessarily
1152            let over_column_width =
1153                is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
1154            let should_hang = same_op_level
1155                || over_column_width
1156                || binop_expression_contains_comments(&full_expression, &binop);
1157
1158            // Only use the indented shape if we are planning to hang
1159            let shape = if should_hang { test_shape } else { shape };
1160
1161            let mut new_binop = format_binop(ctx, &binop, shape);
1162            if should_hang {
1163                new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
1164            }
1165
1166            let (lhs, rhs) = match should_hang {
1167                true => {
1168                    let lhs_shape = shape;
1169                    let rhs_shape =
1170                        shape.reset() + strip_trivia(&new_binop).to_string().len() + SPACE_LEN;
1171
1172                    let (lhs, rhs) = match side_to_hang {
1173                        ExpressionSide::Left => (
1174                            hang_binop_expression(
1175                                ctx,
1176                                *lhs,
1177                                if same_op_level {
1178                                    top_binop
1179                                } else {
1180                                    binop.clone()
1181                                },
1182                                lhs_shape,
1183                                lhs_range,
1184                                expression_context,
1185                            ),
1186                            if contains_comments(&*rhs) {
1187                                hang_binop_expression(
1188                                    ctx,
1189                                    *rhs,
1190                                    binop,
1191                                    shape,
1192                                    lhs_range,
1193                                    expression_context,
1194                                )
1195                            } else {
1196                                format_expression_internal(
1197                                    ctx,
1198                                    &rhs,
1199                                    ExpressionContext::UnaryOrBinary,
1200                                    rhs_shape,
1201                                )
1202                            },
1203                        ),
1204                        ExpressionSide::Right => (
1205                            if contains_comments(&*lhs) {
1206                                hang_binop_expression(
1207                                    ctx,
1208                                    *lhs,
1209                                    binop.clone(),
1210                                    shape,
1211                                    lhs_range,
1212                                    expression_context,
1213                                )
1214                            } else {
1215                                let context = if let BinOp::Caret(_) = binop {
1216                                    ExpressionContext::BinaryLHSExponent
1217                                } else {
1218                                    ExpressionContext::BinaryLHS
1219                                };
1220                                format_expression_internal(ctx, &lhs, context, lhs_shape)
1221                            },
1222                            hang_binop_expression(
1223                                ctx,
1224                                *rhs,
1225                                if same_op_level { top_binop } else { binop },
1226                                rhs_shape,
1227                                lhs_range,
1228                                expression_context,
1229                            ),
1230                        ),
1231                    };
1232                    (
1233                        lhs,
1234                        rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1235                    )
1236                }
1237                false => {
1238                    // Check if the chain still has comments deeper inside of it.
1239                    // If it does, we need to hang that part of the chain still, otherwise the comments will mess it up
1240                    let lhs = if contains_comments(&*lhs) {
1241                        hang_binop_expression(
1242                            ctx,
1243                            *lhs,
1244                            binop.to_owned(),
1245                            shape,
1246                            lhs_range,
1247                            expression_context,
1248                        )
1249                    } else {
1250                        let context = if let BinOp::Caret(_) = binop {
1251                            ExpressionContext::BinaryLHSExponent
1252                        } else {
1253                            ExpressionContext::BinaryLHS
1254                        };
1255                        format_expression_internal(ctx, &lhs, context, shape)
1256                    };
1257
1258                    let rhs = if contains_comments(&*rhs) {
1259                        hang_binop_expression(
1260                            ctx,
1261                            *rhs,
1262                            binop,
1263                            shape,
1264                            lhs_range,
1265                            expression_context,
1266                        )
1267                    } else {
1268                        format_expression_internal(
1269                            ctx,
1270                            &rhs,
1271                            ExpressionContext::UnaryOrBinary,
1272                            shape,
1273                        )
1274                    };
1275
1276                    (lhs, rhs)
1277                }
1278            };
1279
1280            Expression::BinaryOperator {
1281                lhs: Box::new(lhs),
1282                binop: new_binop,
1283                rhs: Box::new(rhs),
1284            }
1285        }
1286        // Base case: no more binary operators - just return to normal splitting
1287        _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1288    }
1289}
1290
1291/// Internal expression formatter, where the binop is also hung
1292fn format_hanging_expression_(
1293    ctx: &Context,
1294    expression: &Expression,
1295    shape: Shape,
1296    expression_context: ExpressionContext,
1297    lhs_range: Option<LeftmostRangeHang>,
1298) -> Expression {
1299    let expression_range = expression.to_range();
1300
1301    match expression {
1302        #[cfg(feature = "luau")]
1303        Expression::TypeAssertion {
1304            expression,
1305            type_assertion,
1306        } => {
1307            // If we have a type assertion, we increment the current shape with the size of the assertion
1308            // to "force" the parentheses to hang if necessary
1309            let (expression_context, value_shape) = (
1310                ExpressionContext::TypeAssertion,
1311                shape.take_first_line(&strip_trivia(type_assertion)),
1312            );
1313
1314            let expression = format_hanging_expression_(
1315                ctx,
1316                expression,
1317                value_shape,
1318                expression_context,
1319                lhs_range,
1320            );
1321
1322            // Update the shape used to format the type assertion
1323            #[cfg(feature = "luau")]
1324            let assertion_shape = shape.take_last_line(&expression);
1325
1326            Expression::TypeAssertion {
1327                expression: Box::new(expression),
1328                type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1329            }
1330        }
1331        Expression::Parentheses {
1332            contained,
1333            expression,
1334        } => {
1335            let lhs_shape = if let Some(lhs_hang) = lhs_range {
1336                lhs_hang.required_shape(shape, &expression_range)
1337            } else {
1338                shape
1339            };
1340            #[cfg(feature = "luau")]
1341            let keep_parentheses = matches!(
1342                expression_context,
1343                ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1344            );
1345            #[cfg(not(feature = "luau"))]
1346            let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1347
1348            // Examine whether the internal expression requires parentheses
1349            // If not, just format and return the internal expression. Otherwise, format the parentheses
1350            let use_internal_expression = check_excess_parentheses(expression, expression_context);
1351
1352            // If the context is for a prefix, we should always keep the parentheses, as they are always required
1353            if use_internal_expression && !keep_parentheses {
1354                format_hanging_expression_(
1355                    ctx,
1356                    expression,
1357                    lhs_shape,
1358                    expression_context,
1359                    lhs_range,
1360                )
1361            } else {
1362                let contained = format_contained_span(ctx, contained, lhs_shape);
1363
1364                // Provide a sample formatting to see how large it is
1365                // Examine the expression itself to see if needs to be split onto multiple lines
1366                let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); // 1 = opening parentheses
1367
1368                let expression_str = formatted_expression.to_string();
1369                if !contains_comments(expression)
1370                    && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1371                {
1372                    // The expression inside the parentheses is small, we do not need to break it down further
1373                    return Expression::Parentheses {
1374                        contained,
1375                        expression: Box::new(formatted_expression),
1376                    };
1377                }
1378
1379                // Update the expression shape to be used inside the parentheses, applying the indent increase
1380                let expression_shape = lhs_shape.reset().increment_additional_indent();
1381
1382                // Modify the parentheses to hang the expression
1383                let (start_token, end_token) = contained.tokens();
1384
1385                // Create a newline after the start brace and before the end brace
1386                // Also, indent enough for the first expression in the start brace
1387                let contained = ContainedSpan::new(
1388                    start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1389                        create_newline_trivia(ctx),
1390                        create_indent_trivia(ctx, expression_shape),
1391                    ])),
1392                    end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1393                        create_newline_trivia(ctx),
1394                        create_indent_trivia(ctx, lhs_shape),
1395                    ])),
1396                );
1397
1398                Expression::Parentheses {
1399                    contained,
1400                    expression: Box::new(format_hanging_expression_(
1401                        ctx,
1402                        expression,
1403                        expression_shape,
1404                        ExpressionContext::Standard,
1405                        None,
1406                    )),
1407                }
1408            }
1409        }
1410        Expression::UnaryOperator { unop, expression } => {
1411            let unop = format_unop(ctx, unop, shape);
1412            let shape = shape + strip_leading_trivia(&unop).to_string().len();
1413            let expression = format_hanging_expression_(
1414                ctx,
1415                expression,
1416                shape,
1417                ExpressionContext::UnaryOrBinary,
1418                lhs_range,
1419            );
1420
1421            Expression::UnaryOperator {
1422                unop,
1423                expression: Box::new(expression),
1424            }
1425        }
1426        Expression::BinaryOperator { lhs, binop, rhs } => {
1427            // Don't format the lhs and rhs here, because it will be handled later when hang_binop_expression calls back for a Value
1428            let lhs = hang_binop_expression(
1429                ctx,
1430                *lhs.to_owned(),
1431                binop.to_owned(),
1432                shape,
1433                lhs_range,
1434                ExpressionContext::UnaryOrBinary,
1435            );
1436
1437            let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop
1438            let mut new_binop = format_binop(ctx, binop, current_shape);
1439
1440            let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; // 1 = space after binop
1441
1442            let mut new_rhs = hang_binop_expression(
1443                ctx,
1444                *rhs.to_owned(),
1445                binop.to_owned(),
1446                singleline_shape,
1447                None,
1448                ExpressionContext::Standard,
1449            );
1450
1451            // Examine the last line to see if we need to hang this binop, or if the precedence levels match
1452            if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1453                || (did_hang_expression(&new_rhs)
1454                    && binop_precedence_level(&new_rhs) >= binop.precedence())
1455                || contains_comments(binop)
1456                || lhs.has_trailing_comments(CommentSearch::All)
1457                || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1458            {
1459                let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1460                new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1461                new_rhs = hang_binop_expression(
1462                    ctx,
1463                    *rhs.to_owned(),
1464                    binop.to_owned(),
1465                    hanging_shape,
1466                    None,
1467                    ExpressionContext::Standard,
1468                )
1469                .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1470            }
1471
1472            Expression::BinaryOperator {
1473                lhs: Box::new(lhs),
1474                binop: new_binop,
1475                rhs: Box::new(new_rhs),
1476            }
1477        }
1478        _ => {
1479            let value_shape = if let Some(lhs_hang) = lhs_range {
1480                lhs_hang.required_shape(shape, &expression_range)
1481            } else {
1482                shape
1483            };
1484
1485            format_expression_internal(ctx, expression, expression_context, value_shape)
1486        }
1487    }
1488}
1489
1490pub fn hang_expression(
1491    ctx: &Context,
1492    expression: &Expression,
1493    shape: Shape,
1494    hang_level: Option<usize>,
1495) -> Expression {
1496    let original_additional_indent_level = shape.indent().additional_indent();
1497    let shape = match hang_level {
1498        Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1499        None => shape,
1500    };
1501
1502    let lhs_range =
1503        hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1504
1505    format_hanging_expression_(
1506        ctx,
1507        expression,
1508        shape,
1509        ExpressionContext::Standard,
1510        lhs_range,
1511    )
1512}
1513
1514pub fn hang_expression_trailing_newline(
1515    ctx: &Context,
1516    expression: &Expression,
1517    shape: Shape,
1518    hang_level: Option<usize>,
1519) -> Expression {
1520    hang_expression(ctx, expression, shape, hang_level)
1521        .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1522}