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