Skip to main content

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