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        Expression::Parentheses { expression, .. } => is_brackets_string(expression),
373        #[cfg(feature = "luau")]
374        Expression::TypeAssertion { expression, .. } => is_brackets_string(expression),
375        _ => false,
376    }
377}
378
379pub fn process_dot_name(
380    ctx: &Context,
381    dot: &TokenReference,
382    name: &TokenReference,
383    shape: Shape,
384) -> (TokenReference, TokenReference) {
385    // If there are any comments in between the dot and name,
386    // then taken them out and put them before the dot
387    let (mut dot, mut dot_comments) =
388        take_trailing_comments(&format_token_reference(ctx, dot, shape));
389    let (name, name_comments) = take_leading_comments(&format_token_reference(ctx, name, shape));
390
391    dot_comments.extend(name_comments);
392
393    if !dot_comments.is_empty() {
394        dot = prepend_newline_indent(
395            ctx,
396            &dot.update_leading_trivia(FormatTriviaType::Append(dot_comments)),
397            shape,
398        );
399    }
400
401    (dot, name)
402}
403
404/// Formats an Index Node
405pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
406    match index {
407        Index::Brackets {
408            brackets,
409            expression,
410        } => {
411            if brackets
412                .tokens()
413                .0
414                .has_trailing_comments(CommentSearch::All)
415                || contains_comments(expression)
416                || brackets.tokens().1.has_leading_comments(CommentSearch::All)
417            {
418                let (start_bracket, end_bracket) = brackets.tokens();
419
420                let indent_shape = shape.reset().increment_additional_indent();
421
422                // Format the brackets multiline
423                let brackets = ContainedSpan::new(
424                    fmt_symbol!(ctx, start_bracket, "[", shape).update_trailing_trivia(
425                        FormatTriviaType::Append(vec![
426                            create_newline_trivia(ctx),
427                            create_indent_trivia(ctx, indent_shape),
428                        ]),
429                    ),
430                    format_end_token(ctx, end_bracket, EndTokenType::IndentComments, shape)
431                        .update_leading_trivia(FormatTriviaType::Append(vec![
432                            create_indent_trivia(ctx, shape),
433                        ])),
434                );
435
436                let expression = format_expression(ctx, expression, indent_shape)
437                    .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(
438                        ctx,
439                    )]));
440
441                Index::Brackets {
442                    brackets,
443                    expression,
444                }
445            } else if is_brackets_string(expression) {
446                Index::Brackets {
447                    brackets: format_contained_span(ctx, brackets, shape),
448                    expression: format_expression(ctx, expression, shape + 2) // 2 = "[ "
449                        .update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
450                            TokenType::spaces(1),
451                        )]))
452                        .update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
453                            TokenType::spaces(1),
454                        )])),
455                }
456            } else {
457                Index::Brackets {
458                    brackets: format_contained_span(ctx, brackets, shape),
459                    expression: format_expression(ctx, expression, shape + 1), // 1 = opening bracket
460                }
461            }
462        }
463
464        Index::Dot { dot, name } => {
465            let (dot, name) = process_dot_name(ctx, dot, name, shape);
466            Index::Dot { dot, name }
467        }
468        other => panic!("unknown node {:?}", other),
469    }
470}
471
472// Checks if this is a string (allows strings wrapped in parentheses)
473fn is_string(expression: &Expression) -> bool {
474    match expression {
475        Expression::String(_) => true,
476        #[cfg(feature = "luau")]
477        Expression::InterpolatedString(_) => true,
478        Expression::Parentheses { expression, .. } => is_string(expression),
479        _ => false,
480    }
481}
482
483/// Formats a Prefix Node
484pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
485    match prefix {
486        Prefix::Expression(expression) => {
487            let singleline_format =
488                format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
489            let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
490
491            if singeline_shape.over_budget() && !is_string(expression) {
492                Prefix::Expression(Box::new(format_hanging_expression_(
493                    ctx,
494                    expression,
495                    shape,
496                    ExpressionContext::Prefix,
497                    None,
498                )))
499            } else {
500                Prefix::Expression(Box::new(singleline_format))
501            }
502        }
503        Prefix::Name(token_reference) => {
504            Prefix::Name(format_token_reference(ctx, token_reference, shape))
505        }
506        other => panic!("unknown node {:?}", other),
507    }
508}
509
510/// Formats a Suffix Node
511pub fn format_suffix(
512    ctx: &Context,
513    suffix: &Suffix,
514    shape: Shape,
515    call_next_node: FunctionCallNextNode,
516) -> Suffix {
517    match suffix {
518        Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
519        Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
520        other => panic!("unknown node {:?}", other),
521    }
522}
523
524/// Formats and else if expression onto a single line.
525/// This function does not take into account for comments
526#[cfg(feature = "luau")]
527fn format_else_if_expression_singleline(
528    ctx: &Context,
529    else_if_expression: &ElseIfExpression,
530    shape: Shape,
531) -> ElseIfExpression {
532    let else_if_token = fmt_symbol!(ctx, else_if_expression.else_if_token(), "elseif ", shape);
533    let else_if_condition = remove_condition_parentheses(else_if_expression.condition().to_owned());
534    let else_if_condition = format_expression(ctx, &else_if_condition, shape + 7); // 7 = "elseif "
535    let (then_token, expression) = format_token_expression_sequence(
536        ctx,
537        else_if_expression.then_token(),
538        else_if_expression.expression(),
539        shape.take_first_line(&else_if_condition) + 13, // 13 = "elseif " + " then ",
540    );
541
542    // Add a space before the then token
543    let then_token = then_token.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
544        TokenType::spaces(1),
545    )]));
546
547    ElseIfExpression::new(else_if_condition, expression)
548        .with_else_if_token(else_if_token)
549        .with_then_token(then_token)
550}
551
552/// Formats a `<token> <expr>` sequence, such as `then <expr>` or `else <expr>`.
553/// In particular, this handles when the <expr> has to be formatted onto multiple lines (either due to comments, or going over width)
554#[cfg(feature = "luau")]
555fn format_token_expression_sequence(
556    ctx: &Context,
557    token: &TokenReference,
558    expression: &Expression,
559    shape: Shape,
560) -> (TokenReference, Expression) {
561    const SPACE_LEN: usize = " ".len();
562    let formatted_token = format_token_reference(ctx, token, shape);
563    let token_width = strip_trivia(&formatted_token).to_string().len();
564
565    let formatted_expression =
566        format_expression(ctx, expression, shape.add_width(token_width + SPACE_LEN));
567
568    let requires_multiline_expression = shape.take_first_line(&formatted_expression).over_budget()
569        || token.has_trailing_comments(CommentSearch::All)
570        || trivia_util::contains_comments(
571            expression.update_trailing_trivia(FormatTriviaType::Replace(vec![])),
572        ); // Remove trailing trivia (comments) before checking, as they shouldn't have an impact
573
574    let newline_after_token = token.has_trailing_comments(CommentSearch::Single)
575        || expression.has_leading_comments(CommentSearch::Single);
576
577    let token = match newline_after_token {
578        // `<token>\n`
579        true => formatted_token
580            .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
581        // `<token> `
582        false => {
583            formatted_token.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
584                TokenType::spaces(1),
585            )]))
586        }
587    };
588
589    let expression = match requires_multiline_expression {
590        true => match newline_after_token {
591            true => {
592                let shape = shape.reset().increment_additional_indent();
593                hang_expression(ctx, expression, shape, calculate_hang_level(expression))
594                    .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
595                        ctx, shape,
596                    )]))
597            }
598            false => hang_expression(
599                ctx,
600                expression,
601                shape.add_width(token_width + SPACE_LEN),
602                calculate_hang_level(expression),
603            ),
604        },
605        false => formatted_expression,
606    };
607
608    (token, expression)
609}
610
611/// Formats an [`IfExpression`] node
612#[cfg(feature = "luau")]
613fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
614    // Remove parentheses around the condition
615    let condition = remove_condition_parentheses(if_expression.condition().to_owned());
616    let if_token = fmt_symbol!(ctx, if_expression.if_token(), "if ", shape);
617
618    // Initially format the remainder on a single line
619    let singleline_condition = format_expression(ctx, &condition, shape.with_infinite_width());
620    let then_token = fmt_symbol!(ctx, if_expression.then_token(), " then ", shape);
621    let singleline_expression = format_expression(
622        ctx,
623        if_expression.if_expression(),
624        shape.with_infinite_width(),
625    );
626    let else_ifs = if_expression
627        .else_if_expressions()
628        .map(|else_if_expressions| {
629            else_if_expressions
630                .iter()
631                .map(|else_if_expression| {
632                    format_else_if_expression_singleline(
633                        ctx,
634                        else_if_expression,
635                        shape.with_infinite_width(),
636                    )
637                })
638                .collect::<Vec<_>>()
639        });
640    let else_token = fmt_symbol!(ctx, if_expression.else_token(), " else ", shape);
641    let singleline_else_expression = format_expression(
642        ctx,
643        if_expression.else_expression(),
644        shape.with_infinite_width(),
645    );
646
647    const IF_LENGTH: usize = 3; // "if "
648    const THEN_LENGTH: usize = 6; // " then "
649    const ELSE_LENGTH: usize = 6; // " else "
650
651    // Determine if we need to hang the expression
652    let singleline_shape = (shape + IF_LENGTH + THEN_LENGTH + ELSE_LENGTH)
653        .take_first_line(&strip_trivia(&singleline_condition))
654        .take_first_line(&strip_trivia(&singleline_expression))
655        .take_first_line(&else_ifs.as_ref().map_or(String::new(), |x| {
656            x.iter().map(|x| x.to_string()).collect::<String>()
657        }))
658        .take_first_line(&strip_trivia(&singleline_else_expression));
659
660    let require_multiline_expression = singleline_shape.over_budget()
661        || if_expression
662            .if_token()
663            .has_trailing_comments(CommentSearch::All)
664        || trivia_util::contains_comments(if_expression.condition())
665        || trivia_util::contains_comments(if_expression.then_token())
666        || trivia_util::contains_comments(if_expression.if_expression())
667        || trivia_util::contains_comments(if_expression.else_token())
668        || if_expression
669            .else_if_expressions()
670            .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::contains_comments))
671        || if_expression.else_expression().has_inline_comments()
672        || trivia_util::spans_multiple_lines(&singleline_condition)
673        || trivia_util::spans_multiple_lines(&singleline_expression)
674        || else_ifs
675            .as_ref()
676            .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::spans_multiple_lines))
677        || trivia_util::spans_multiple_lines(&singleline_else_expression);
678
679    if require_multiline_expression {
680        let condition = hang_expression_trailing_newline(
681            ctx,
682            if_expression.condition(),
683            shape.increment_additional_indent(),
684            Some(1),
685        );
686        let hanging_shape = shape.reset().increment_additional_indent();
687
688        // then <expr>
689        let (then_token, expression) = format_token_expression_sequence(
690            ctx,
691            if_expression.then_token(),
692            if_expression.if_expression(),
693            hanging_shape,
694        );
695
696        // Indent the then token
697        let then_token =
698            then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
699                ctx,
700                hanging_shape,
701            )]));
702
703        // elseif <condition> then <expr>
704        let else_ifs = if_expression
705            .else_if_expressions()
706            .map(|else_if_expressions| {
707                else_if_expressions
708                    .iter()
709                    .map(|else_if_expression| {
710                        let singleline_else_if = format_else_if_expression_singleline(
711                            ctx,
712                            else_if_expression,
713                            hanging_shape,
714                        );
715                        let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
716
717                        if singleline_shape.over_budget()
718                            || else_if_expression
719                                .else_if_token()
720                                .has_trailing_comments(CommentSearch::All)
721                            || trivia_util::contains_comments(else_if_expression.condition())
722                            || trivia_util::contains_comments(else_if_expression.then_token())
723                        {
724                            let else_if_token = fmt_symbol!(
725                                ctx,
726                                else_if_expression.else_if_token(),
727                                "elseif",
728                                shape
729                            )
730                            .update_leading_trivia(FormatTriviaType::Append(vec![
731                                create_newline_trivia(ctx),
732                                create_indent_trivia(ctx, hanging_shape),
733                            ]));
734
735                            let condiiton_shape =
736                                hanging_shape.reset().increment_additional_indent();
737                            let else_if_condition = hang_expression(
738                                ctx,
739                                &remove_condition_parentheses(
740                                    else_if_expression.condition().to_owned(),
741                                ),
742                                condiiton_shape,
743                                None,
744                            )
745                            .update_leading_trivia(FormatTriviaType::Append(vec![
746                                create_newline_trivia(ctx),
747                                create_indent_trivia(ctx, condiiton_shape),
748                            ]));
749
750                            let hanging_shape =
751                                hanging_shape.take_first_line(&else_if_condition) + 13; // 13 = "elseif " + " then "
752
753                            let (then_token, expression) = format_token_expression_sequence(
754                                ctx,
755                                else_if_expression.then_token(),
756                                else_if_expression.expression(),
757                                hanging_shape,
758                            );
759
760                            let then_token =
761                                then_token.update_leading_trivia(FormatTriviaType::Append(vec![
762                                    create_newline_trivia(ctx),
763                                    create_indent_trivia(ctx, hanging_shape),
764                                ]));
765
766                            ElseIfExpression::new(else_if_condition, expression)
767                                .with_else_if_token(else_if_token)
768                                .with_then_token(then_token)
769                        } else {
770                            singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
771                                vec![
772                                    create_newline_trivia(ctx),
773                                    create_indent_trivia(ctx, hanging_shape),
774                                ],
775                            ))
776                        }
777                    })
778                    .collect::<Vec<_>>()
779            });
780
781        // else <expr>
782        let (else_token, else_expression) = format_token_expression_sequence(
783            ctx,
784            if_expression.else_token(),
785            if_expression.else_expression(),
786            hanging_shape + 5, // 5 = "else "
787        );
788
789        // Put the else on a new line
790        let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
791
792        IfExpression::new(condition, expression, else_expression)
793            .with_if_token(if_token)
794            .with_then_token(then_token)
795            .with_else_if(else_ifs)
796            .with_else_token(else_token)
797    } else {
798        // Prepend a space before each else if
799        let else_ifs = else_ifs.map(|x| {
800            x.iter()
801                .map(|x| {
802                    x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
803                        TokenType::spaces(1),
804                    )]))
805                })
806                .collect()
807        });
808
809        IfExpression::new(
810            singleline_condition,
811            singleline_expression,
812            singleline_else_expression,
813        )
814        .with_if_token(if_token)
815        .with_then_token(then_token)
816        .with_else_if(else_ifs)
817        .with_else_token(else_token)
818    }
819}
820
821#[cfg(feature = "luau")]
822fn format_interpolated_string(
823    ctx: &Context,
824    interpolated_string: &InterpolatedString,
825    shape: Shape,
826) -> InterpolatedString {
827    let mut shape = shape;
828
829    let mut segments = Vec::new();
830    for segment in interpolated_string.segments() {
831        let literal = format_token_reference(ctx, &segment.literal, shape);
832        shape = shape + literal.to_string().len();
833
834        let mut expression = format_expression(ctx, &segment.expression, shape);
835        shape = shape.take_last_line(&expression);
836
837        // If expression is a table constructor, then ensure a space is added beforehand
838        // since `{{` syntax is not permitted
839        if let Expression::TableConstructor { .. } = expression {
840            expression =
841                expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
842                    TokenType::spaces(1),
843                )]))
844        }
845
846        segments.push(InterpolatedStringSegment {
847            literal,
848            expression,
849        })
850    }
851
852    interpolated_string
853        .to_owned()
854        .with_segments(segments)
855        .with_last_string(format_token_reference(
856            ctx,
857            interpolated_string.last_string(),
858            shape,
859        ))
860}
861
862/// Formats a Var Node
863pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
864    match var {
865        Var::Name(token_reference) => {
866            Var::Name(format_token_reference(ctx, token_reference, shape))
867        }
868        Var::Expression(var_expression) => {
869            Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
870        }
871        other => panic!("unknown node {:?}", other),
872    }
873}
874
875pub fn format_var_expression(
876    ctx: &Context,
877    var_expression: &VarExpression,
878    shape: Shape,
879) -> VarExpression {
880    // A VarExpression is pretty much exactly the same as FunctionCall, so we repurpose
881    // format_function_call for that, and reuse its output
882    let function_call = format_function_call(
883        ctx,
884        &FunctionCall::new(var_expression.prefix().clone())
885            .with_suffixes(var_expression.suffixes().cloned().collect()),
886        shape,
887    );
888    VarExpression::new(function_call.prefix().clone())
889        .with_suffixes(function_call.suffixes().cloned().collect())
890}
891
892/// Formats an UnOp Node
893pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
894    fmt_op!(ctx, UnOp, unop, shape, {
895        Minus = "-",
896        Not = "not ",
897        Hash = "#",
898        #[cfg(feature = "lua53")]
899        Tilde = "~",
900    }, |other| panic!("unknown node {:?}", other))
901}
902
903/// Pushes a [`BinOp`] onto a newline, and indent its depending on indent_level.
904/// Preserves any leading comments, and moves trailing comments to before the BinOp.
905/// Also takes in the [`Expression`] present on the RHS of the BinOp - this is needed so that we can take any
906/// leading comments from the expression, and place them before the BinOp.
907fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
908    // Get the leading comments of a binop, as we need to preserve them
909    // Intersperse a newline and indent trivia between them
910    // iter_intersperse is currently not available, so we need to do something different. Tracking issue: https://github.com/rust-lang/rust/issues/79524
911    let mut leading_comments = binop
912        .leading_comments()
913        .iter()
914        .flat_map(|x| {
915            vec![
916                create_newline_trivia(ctx),
917                create_indent_trivia(ctx, shape),
918                x.to_owned(),
919            ]
920        })
921        .collect::<Vec<_>>();
922
923    // If there are any comments trailing the BinOp, we need to move them to before the BinOp
924    let mut trailing_comments = binop.trailing_comments();
925    leading_comments.append(&mut trailing_comments);
926
927    // If there are any leading comments to the RHS expression, we need to move them to before the BinOp
928    let mut expression_leading_comments = rhs
929        .leading_comments()
930        .iter()
931        .flat_map(|x| {
932            vec![
933                create_newline_trivia(ctx),
934                create_indent_trivia(ctx, shape),
935                x.to_owned(),
936            ]
937        })
938        .collect::<Vec<_>>();
939    leading_comments.append(&mut expression_leading_comments);
940
941    // Create a newline just before the BinOp, and preserve the indentation
942    leading_comments.push(create_newline_trivia(ctx));
943    leading_comments.push(create_indent_trivia(ctx, shape));
944
945    binop.update_trivia(
946        FormatTriviaType::Replace(leading_comments),
947        FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
948    )
949}
950
951/// Finds the length of the expression which matches the precedence level of the provided binop
952fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
953    match expression {
954        Expression::BinaryOperator { lhs, binop, rhs } => {
955            if binop.precedence() >= top_binop.precedence()
956                && binop.is_right_associative() == top_binop.is_right_associative()
957            {
958                if binop.is_right_associative() {
959                    binop_expression_length(rhs, top_binop)
960                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
961                        + strip_trivia(&**lhs).to_string().len()
962                } else {
963                    binop_expression_length(lhs, top_binop)
964                        + strip_trivia(binop).to_string().len() + 2 // 2 = space before and after binop
965                        + strip_trivia(&**rhs).to_string().len()
966                }
967            } else {
968                0
969            }
970        }
971        _ => strip_trivia(expression).to_string().len(),
972    }
973}
974
975fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
976    match expression {
977        Expression::BinaryOperator { lhs, binop, rhs } => {
978            if binop.precedence() == top_binop.precedence() {
979                contains_comments(binop)
980                    || rhs.has_leading_comments(CommentSearch::All)
981                    || lhs.has_trailing_comments(CommentSearch::All)
982                    || binop_expression_contains_comments(lhs, top_binop)
983                    || binop_expression_contains_comments(rhs, top_binop)
984            } else {
985                false
986            }
987        }
988        _ => false,
989    }
990}
991
992/// Converts an item to a range
993trait ToRange {
994    fn to_range(&self) -> (usize, usize);
995}
996
997impl ToRange for (usize, usize) {
998    fn to_range(&self) -> (usize, usize) {
999        *self
1000    }
1001}
1002
1003impl ToRange for Expression {
1004    fn to_range(&self) -> (usize, usize) {
1005        let (start, end) = self.range().unwrap();
1006        (start.bytes(), end.bytes())
1007    }
1008}
1009
1010/// This struct encompasses information about the leftmost-expression in a BinaryExpression tree.
1011/// It holds the range of the leftmost binary expression, and the original additional indent level of this range.
1012/// This struct is only used when the hanging binary expression involves a hang level, for example:
1013/// ```lua
1014/// foo
1015///    + bar
1016///    + baz
1017/// ```
1018/// or in a larger context:
1019/// ```lua
1020/// local someVariable = foo
1021///    + bar
1022///    + baz
1023/// ```
1024/// As seen, the first item (`foo`) is inlined, and has an indent level one lower than the rest of the binary
1025/// expressions. We want to ensure that whenever we have `foo` in our expression, we use the original indentation level
1026/// because the expression is (at this current point in time) inlined - otherwise, it will be over-indented.
1027/// We hold the original indentation level in case we are deep down in the recursive calls:
1028/// ```lua
1029/// local ratio = (minAxis - minAxisSize) / delta * (self.props.maxScaleRatio - self.props.minScaleRatio)
1030///     + self.props.minScaleRatio
1031/// ```
1032/// Since the first line contains binary operators at a different precedence level to the `+`, then the indentation
1033/// level has been increased even further. But we want to use the original indentation level, because as it stands,
1034/// the expression is currently inlined on the original line.
1035#[derive(Clone, Copy, Debug)]
1036struct LeftmostRangeHang {
1037    range: (usize, usize),
1038    original_additional_indent_level: usize,
1039}
1040
1041impl LeftmostRangeHang {
1042    /// Finds the leftmost expression from the given (full) expression, and then creates a [`LeftmostRangeHang`]
1043    /// to represent it
1044    fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1045        match expression {
1046            Expression::BinaryOperator { lhs, .. } => {
1047                Self::find(lhs, original_additional_indent_level)
1048            }
1049            _ => Self {
1050                range: expression.to_range(),
1051                original_additional_indent_level,
1052            },
1053        }
1054    }
1055
1056    /// Given an [`Expression`], returns the [`Shape`] to use for this expression.
1057    /// This function checks the provided expression to see if the LeftmostRange falls inside of it.
1058    /// If so, then we need to use the original indentation level shape, as (so far) the expression is inlined.
1059    fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1060        let (expression_start, expression_end) = item.to_range();
1061        let (lhs_start, lhs_end) = self.range;
1062
1063        if lhs_start >= expression_start && lhs_end <= expression_end {
1064            shape.with_indent(
1065                shape
1066                    .indent()
1067                    .with_additional_indent(self.original_additional_indent_level),
1068            )
1069        } else {
1070            shape
1071        }
1072    }
1073}
1074
1075fn is_hang_binop_over_width(
1076    shape: Shape,
1077    expression: &Expression,
1078    top_binop: &BinOp,
1079    lhs_range: Option<LeftmostRangeHang>,
1080) -> bool {
1081    let shape = if let Some(lhs_hang) = lhs_range {
1082        lhs_hang.required_shape(shape, expression)
1083    } else {
1084        shape
1085    };
1086
1087    shape
1088        .add_width(binop_expression_length(expression, top_binop))
1089        .over_budget()
1090}
1091
1092/// If present, finds the precedence level of the provided binop in the BinOp expression. Otherwise, returns 0
1093fn binop_precedence_level(expression: &Expression) -> u8 {
1094    match expression {
1095        Expression::BinaryOperator { binop, .. } => binop.precedence(),
1096        _ => 0,
1097    }
1098}
1099
1100fn did_hang_expression(expression: &Expression) -> bool {
1101    if let Expression::BinaryOperator { binop, .. } = expression {
1102        // Examine the binop's leading trivia for a newline
1103        // TODO: this works..., but is it the right solution?
1104        binop
1105            .surrounding_trivia()
1106            .0
1107            .iter()
1108            .any(|x| trivia_is_newline(x))
1109    } else {
1110        false
1111    }
1112}
1113
1114#[derive(Debug)]
1115enum ExpressionSide {
1116    Left,
1117    Right,
1118}
1119
1120fn hang_binop_expression(
1121    ctx: &Context,
1122    expression: Expression,
1123    top_binop: BinOp,
1124    shape: Shape,
1125    lhs_range: Option<LeftmostRangeHang>,
1126    expression_context: ExpressionContext,
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                                expression_context,
1186                            ),
1187                            if contains_comments(&*rhs) {
1188                                hang_binop_expression(
1189                                    ctx,
1190                                    *rhs,
1191                                    binop,
1192                                    shape,
1193                                    lhs_range,
1194                                    expression_context,
1195                                )
1196                            } else {
1197                                format_expression_internal(
1198                                    ctx,
1199                                    &rhs,
1200                                    ExpressionContext::UnaryOrBinary,
1201                                    rhs_shape,
1202                                )
1203                            },
1204                        ),
1205                        ExpressionSide::Right => (
1206                            if contains_comments(&*lhs) {
1207                                hang_binop_expression(
1208                                    ctx,
1209                                    *lhs,
1210                                    binop.clone(),
1211                                    shape,
1212                                    lhs_range,
1213                                    expression_context,
1214                                )
1215                            } else {
1216                                let context = if let BinOp::Caret(_) = binop {
1217                                    ExpressionContext::BinaryLHSExponent
1218                                } else {
1219                                    ExpressionContext::BinaryLHS
1220                                };
1221                                format_expression_internal(ctx, &lhs, context, lhs_shape)
1222                            },
1223                            hang_binop_expression(
1224                                ctx,
1225                                *rhs,
1226                                if same_op_level { top_binop } else { binop },
1227                                rhs_shape,
1228                                lhs_range,
1229                                expression_context,
1230                            ),
1231                        ),
1232                    };
1233                    (
1234                        lhs,
1235                        rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1236                    )
1237                }
1238                false => {
1239                    // Check if the chain still has comments deeper inside of it.
1240                    // If it does, we need to hang that part of the chain still, otherwise the comments will mess it up
1241                    let lhs = if contains_comments(&*lhs) {
1242                        hang_binop_expression(
1243                            ctx,
1244                            *lhs,
1245                            binop.to_owned(),
1246                            shape,
1247                            lhs_range,
1248                            expression_context,
1249                        )
1250                    } else {
1251                        let context = if let BinOp::Caret(_) = binop {
1252                            ExpressionContext::BinaryLHSExponent
1253                        } else {
1254                            ExpressionContext::BinaryLHS
1255                        };
1256                        format_expression_internal(ctx, &lhs, context, shape)
1257                    };
1258
1259                    let rhs = if contains_comments(&*rhs) {
1260                        hang_binop_expression(
1261                            ctx,
1262                            *rhs,
1263                            binop,
1264                            shape,
1265                            lhs_range,
1266                            expression_context,
1267                        )
1268                    } else {
1269                        format_expression_internal(
1270                            ctx,
1271                            &rhs,
1272                            ExpressionContext::UnaryOrBinary,
1273                            shape,
1274                        )
1275                    };
1276
1277                    (lhs, rhs)
1278                }
1279            };
1280
1281            Expression::BinaryOperator {
1282                lhs: Box::new(lhs),
1283                binop: new_binop,
1284                rhs: Box::new(rhs),
1285            }
1286        }
1287        // Base case: no more binary operators - just return to normal splitting
1288        _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1289    }
1290}
1291
1292/// Internal expression formatter, where the binop is also hung
1293fn format_hanging_expression_(
1294    ctx: &Context,
1295    expression: &Expression,
1296    shape: Shape,
1297    expression_context: ExpressionContext,
1298    lhs_range: Option<LeftmostRangeHang>,
1299) -> Expression {
1300    let expression_range = expression.to_range();
1301
1302    match expression {
1303        #[cfg(feature = "luau")]
1304        Expression::TypeAssertion {
1305            expression,
1306            type_assertion,
1307        } => {
1308            // If we have a type assertion, we increment the current shape with the size of the assertion
1309            // to "force" the parentheses to hang if necessary
1310            let (expression_context, value_shape) = (
1311                ExpressionContext::TypeAssertion,
1312                shape.take_first_line(&strip_trivia(type_assertion)),
1313            );
1314
1315            let expression = format_hanging_expression_(
1316                ctx,
1317                expression,
1318                value_shape,
1319                expression_context,
1320                lhs_range,
1321            );
1322
1323            // Update the shape used to format the type assertion
1324            #[cfg(feature = "luau")]
1325            let assertion_shape = shape.take_last_line(&expression);
1326
1327            Expression::TypeAssertion {
1328                expression: Box::new(expression),
1329                type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1330            }
1331        }
1332        Expression::Parentheses {
1333            contained,
1334            expression,
1335        } => {
1336            let lhs_shape = if let Some(lhs_hang) = lhs_range {
1337                lhs_hang.required_shape(shape, &expression_range)
1338            } else {
1339                shape
1340            };
1341            #[cfg(feature = "luau")]
1342            let keep_parentheses = matches!(
1343                expression_context,
1344                ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1345            );
1346            #[cfg(not(feature = "luau"))]
1347            let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1348
1349            // Examine whether the internal expression requires parentheses
1350            // If not, just format and return the internal expression. Otherwise, format the parentheses
1351            let use_internal_expression = check_excess_parentheses(expression, expression_context);
1352
1353            // If the context is for a prefix, we should always keep the parentheses, as they are always required
1354            if use_internal_expression && !keep_parentheses {
1355                format_hanging_expression_(
1356                    ctx,
1357                    expression,
1358                    lhs_shape,
1359                    expression_context,
1360                    lhs_range,
1361                )
1362            } else {
1363                let contained = format_contained_span(ctx, contained, lhs_shape);
1364
1365                // Provide a sample formatting to see how large it is
1366                // Examine the expression itself to see if needs to be split onto multiple lines
1367                let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); // 1 = opening parentheses
1368
1369                let expression_str = formatted_expression.to_string();
1370                if !contains_comments(expression)
1371                    && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1372                {
1373                    // The expression inside the parentheses is small, we do not need to break it down further
1374                    return Expression::Parentheses {
1375                        contained,
1376                        expression: Box::new(formatted_expression),
1377                    };
1378                }
1379
1380                // Update the expression shape to be used inside the parentheses, applying the indent increase
1381                let expression_shape = lhs_shape.reset().increment_additional_indent();
1382
1383                // Modify the parentheses to hang the expression
1384                let (start_token, end_token) = contained.tokens();
1385
1386                // Create a newline after the start brace and before the end brace
1387                // Also, indent enough for the first expression in the start brace
1388                let contained = ContainedSpan::new(
1389                    start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1390                        create_newline_trivia(ctx),
1391                        create_indent_trivia(ctx, expression_shape),
1392                    ])),
1393                    end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1394                        create_newline_trivia(ctx),
1395                        create_indent_trivia(ctx, lhs_shape),
1396                    ])),
1397                );
1398
1399                Expression::Parentheses {
1400                    contained,
1401                    expression: Box::new(format_hanging_expression_(
1402                        ctx,
1403                        expression,
1404                        expression_shape,
1405                        ExpressionContext::Standard,
1406                        None,
1407                    )),
1408                }
1409            }
1410        }
1411        Expression::UnaryOperator { unop, expression } => {
1412            let unop = format_unop(ctx, unop, shape);
1413            let shape = shape + strip_leading_trivia(&unop).to_string().len();
1414            let expression = format_hanging_expression_(
1415                ctx,
1416                expression,
1417                shape,
1418                ExpressionContext::UnaryOrBinary,
1419                lhs_range,
1420            );
1421
1422            Expression::UnaryOperator {
1423                unop,
1424                expression: Box::new(expression),
1425            }
1426        }
1427        Expression::BinaryOperator { lhs, binop, rhs } => {
1428            // Don't format the lhs and rhs here, because it will be handled later when hang_binop_expression calls back for a Value
1429            let lhs = hang_binop_expression(
1430                ctx,
1431                *lhs.to_owned(),
1432                binop.to_owned(),
1433                shape,
1434                lhs_range,
1435                ExpressionContext::UnaryOrBinary,
1436            );
1437
1438            let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop
1439            let mut new_binop = format_binop(ctx, binop, current_shape);
1440
1441            let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; // 1 = space after binop
1442
1443            let mut new_rhs = hang_binop_expression(
1444                ctx,
1445                *rhs.to_owned(),
1446                binop.to_owned(),
1447                singleline_shape,
1448                None,
1449                ExpressionContext::Standard,
1450            );
1451
1452            // Examine the last line to see if we need to hang this binop, or if the precedence levels match
1453            if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1454                || (did_hang_expression(&new_rhs)
1455                    && binop_precedence_level(&new_rhs) >= binop.precedence())
1456                || contains_comments(binop)
1457                || lhs.has_trailing_comments(CommentSearch::All)
1458                || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1459            {
1460                let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1461                new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1462                new_rhs = hang_binop_expression(
1463                    ctx,
1464                    *rhs.to_owned(),
1465                    binop.to_owned(),
1466                    hanging_shape,
1467                    None,
1468                    ExpressionContext::Standard,
1469                )
1470                .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1471            }
1472
1473            Expression::BinaryOperator {
1474                lhs: Box::new(lhs),
1475                binop: new_binop,
1476                rhs: Box::new(new_rhs),
1477            }
1478        }
1479        _ => {
1480            let value_shape = if let Some(lhs_hang) = lhs_range {
1481                lhs_hang.required_shape(shape, &expression_range)
1482            } else {
1483                shape
1484            };
1485
1486            format_expression_internal(ctx, expression, expression_context, value_shape)
1487        }
1488    }
1489}
1490
1491pub fn hang_expression(
1492    ctx: &Context,
1493    expression: &Expression,
1494    shape: Shape,
1495    hang_level: Option<usize>,
1496) -> Expression {
1497    let original_additional_indent_level = shape.indent().additional_indent();
1498    let shape = match hang_level {
1499        Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1500        None => shape,
1501    };
1502
1503    let lhs_range =
1504        hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1505
1506    format_hanging_expression_(
1507        ctx,
1508        expression,
1509        shape,
1510        ExpressionContext::Standard,
1511        lhs_range,
1512    )
1513}
1514
1515pub fn hang_expression_trailing_newline(
1516    ctx: &Context,
1517    expression: &Expression,
1518    shape: Shape,
1519    hang_level: Option<usize>,
1520) -> Expression {
1521    hang_expression(ctx, expression, shape, hang_level)
1522        .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1523}