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,
62 Prefix,
65 #[cfg(feature = "luau")]
69 TypeAssertion,
70
71 BinaryLHS,
75
76 BinaryLHSExponent,
81
82 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
127fn check_excess_parentheses(internal_expression: &Expression, context: ExpressionContext) -> bool {
130 match internal_expression {
131 Expression::Parentheses { .. } => true,
133 Expression::UnaryOperator {
135 expression, unop, ..
136 } => {
137 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 Expression::BinaryOperator { .. } => false,
151
152 #[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 Expression::FunctionCall(_) => false,
170 Expression::Symbol(token_ref) => {
171 match token_ref.token_type() {
172 TokenType::Symbol { symbol } => !matches!(symbol, Symbol::Ellipsis),
175 _ => true,
176 }
177 }
178 #[cfg(feature = "luau")]
181 Expression::IfExpression(_) => false,
182 _ => true,
183 }
184}
185
186pub fn format_expression(ctx: &Context, expression: &Expression, shape: Shape) -> Expression {
188 format_expression_internal(ctx, expression, ExpressionContext::Standard, shape)
189}
190
191fn 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 let use_internal_expression = check_excess_parentheses(expression, context);
255
256 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)), }
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 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 .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 vec![Token::new(TokenType::spaces(1)), x.to_owned()]
337 })
338 .collect();
339 (leading_comments, trailing_comments)
340}
341
342pub 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 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
385pub 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 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) .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), }
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
453fn 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
464pub 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
491pub 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#[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); 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, );
526
527 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#[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 ); 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 true => formatted_token
565 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
566 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#[cfg(feature = "luau")]
598fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
599 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 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; const THEN_LENGTH: usize = 6; const ELSE_LENGTH: usize = 6; 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 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 let then_token =
683 then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
684 ctx,
685 hanging_shape,
686 )]));
687
688 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; 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 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, );
773
774 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 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 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
847pub 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 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
877pub 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
888fn 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
925fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
930 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 let mut trailing_comments = binop.trailing_comments();
947 leading_comments.append(&mut trailing_comments);
948
949 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 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
973fn 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 + strip_trivia(&**lhs).to_string().len()
984 } else {
985 binop_expression_length(lhs, top_binop)
986 + strip_trivia(binop).to_string().len() + 2 + 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
1014trait 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#[derive(Clone, Copy, Debug)]
1058struct LeftmostRangeHang {
1059 range: (usize, usize),
1060 original_additional_indent_level: usize,
1061}
1062
1063impl LeftmostRangeHang {
1064 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 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
1114fn 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 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 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 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 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 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 _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1311 }
1312}
1313
1314fn 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 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 #[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 let use_internal_expression = check_excess_parentheses(expression, expression_context);
1374
1375 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 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); 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 return Expression::Parentheses {
1401 contained,
1402 expression: Box::new(formatted_expression),
1403 };
1404 }
1405
1406 let expression_shape = lhs_shape.reset().increment_additional_indent();
1408
1409 let (start_token, end_token) = contained.tokens();
1411
1412 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 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; let mut new_binop = format_binop(ctx, binop, current_shape);
1467
1468 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; 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 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}