1#[cfg(feature = "luau")]
2use full_moon::ast::luau::{
3 ElseIfExpression, IfExpression, InterpolatedString, InterpolatedStringSegment,
4};
5use full_moon::{
6 ast::{
7 span::ContainedSpan, BinOp, Expression, FunctionCall, Index, Prefix, Suffix, UnOp, Var,
8 VarExpression,
9 },
10 node::Node,
11 tokenizer::{StringLiteralQuoteType, Symbol, Token, TokenReference, TokenType},
12};
13use std::boxed::Box;
14
15#[cfg(feature = "luau")]
16use crate::formatters::{
17 assignment::calculate_hang_level, luau::format_type_assertion,
18 stmt::remove_condition_parentheses, trivia_util::HasInlineComments,
19};
20use crate::{
21 context::{create_indent_trivia, create_newline_trivia, Context},
22 fmt_symbol,
23 formatters::{
24 functions::{
25 format_anonymous_function, format_call, format_function_call, FunctionCallNextNode,
26 },
27 general::{format_contained_span, format_end_token, format_token_reference, EndTokenType},
28 table::format_table_constructor,
29 trivia::{
30 strip_leading_trivia, strip_trivia, FormatTriviaType, UpdateLeadingTrivia,
31 UpdateTrailingTrivia, UpdateTrivia,
32 },
33 trivia_util::{
34 self, contains_comments, prepend_newline_indent, take_leading_comments,
35 take_trailing_comments, trivia_is_newline, CommentSearch, GetLeadingTrivia,
36 GetTrailingTrivia,
37 },
38 },
39 shape::Shape,
40};
41
42#[macro_export]
43macro_rules! fmt_op {
44 ($ctx:expr, $enum:ident, $value:ident, $shape:expr, { $($(#[$inner:meta])* $operator:ident = $output:expr,)+ }, $other:expr) => {
45 match $value {
46 $(
47 $(#[$inner])*
48 $enum::$operator(token) => $enum::$operator(fmt_symbol!($ctx, token, $output, $shape)),
49 )+
50 #[allow(clippy::redundant_closure_call)]
51 other => $other(other),
52 }
53 };
54}
55
56#[derive(Clone, Copy)]
57enum ExpressionContext {
58 Standard,
60 Prefix,
63 #[cfg(feature = "luau")]
67 TypeAssertion,
68
69 BinaryLHS,
73
74 BinaryLHSExponent,
79
80 UnaryOrBinary,
83}
84
85pub fn format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp {
86 fmt_op!(ctx, BinOp, binop, shape, {
87 And = " and ",
88 Caret = " ^ ",
89 GreaterThan = " > ",
90 GreaterThanEqual = " >= ",
91 LessThan = " < ",
92 LessThanEqual = " <= ",
93 Minus = " - ",
94 Or = " or ",
95 Percent = " % ",
96 Plus = " + ",
97 Slash = " / ",
98 Star = " * ",
99 TildeEqual = " ~= ",
100 TwoDots = " .. ",
101 TwoEqual = " == ",
102 #[cfg(feature = "lua53")]
103 Ampersand = " & ",
104 #[cfg(any(feature = "luau", feature = "lua53"))]
105 DoubleSlash = " // ",
106 #[cfg(feature = "lua53")]
107 DoubleLessThan = " << ",
108 #[cfg(feature = "lua53")]
109 Pipe = " | ",
110 #[cfg(feature = "lua53")]
111 Tilde = " ~ ",
112 }, |other: &BinOp| match other {
113 #[cfg(feature = "lua53")]
114 BinOp::DoubleGreaterThan(token) => BinOp::DoubleGreaterThan(
115 format_token_reference(ctx, token, shape)
116 .update_trivia(
117 FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))]),
118 FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))])
119 )
120 ),
121 other => panic!("unknown node {:?}", other)
122 })
123}
124
125fn check_excess_parentheses(internal_expression: &Expression, context: ExpressionContext) -> bool {
128 match internal_expression {
129 Expression::Parentheses { .. } => true,
131 Expression::UnaryOperator {
133 expression, unop, ..
134 } => {
135 if let ExpressionContext::BinaryLHSExponent = context {
138 return false;
139 } else if let ExpressionContext::BinaryLHS = context {
140 if let UnOp::Not(_) = unop {
141 return false;
142 }
143 }
144
145 check_excess_parentheses(expression, context)
146 }
147 Expression::BinaryOperator { .. } => false,
149
150 #[cfg(feature = "luau")]
154 Expression::TypeAssertion { .. }
155 if matches!(
156 context,
157 ExpressionContext::UnaryOrBinary
158 | ExpressionContext::BinaryLHS
159 | ExpressionContext::BinaryLHSExponent
160 ) =>
161 {
162 false
163 }
164
165 Expression::FunctionCall(_) => false,
168 Expression::Symbol(token_ref) => {
169 match token_ref.token_type() {
170 TokenType::Symbol { symbol } => !matches!(symbol, Symbol::Ellipsis),
173 _ => true,
174 }
175 }
176 #[cfg(feature = "luau")]
179 Expression::IfExpression(_) => false,
180 _ => true,
181 }
182}
183
184pub fn format_expression(ctx: &Context, expression: &Expression, shape: Shape) -> Expression {
186 format_expression_internal(ctx, expression, ExpressionContext::Standard, shape)
187}
188
189fn format_expression_internal(
191 ctx: &Context,
192 expression: &Expression,
193 context: ExpressionContext,
194 shape: Shape,
195) -> Expression {
196 match expression {
197 Expression::Function(anonymous_function) => {
198 Expression::Function(format_anonymous_function(ctx, anonymous_function, shape))
199 }
200 Expression::FunctionCall(function_call) => {
201 Expression::FunctionCall(format_function_call(ctx, function_call, shape))
202 }
203 #[cfg(feature = "luau")]
204 Expression::IfExpression(if_expression) => {
205 Expression::IfExpression(format_if_expression(ctx, if_expression, shape))
206 }
207 Expression::Number(token_reference) => {
208 Expression::Number(format_token_reference(ctx, token_reference, shape))
209 }
210 Expression::String(token_reference) => {
211 Expression::String(format_token_reference(ctx, token_reference, shape))
212 }
213 #[cfg(feature = "luau")]
214 Expression::InterpolatedString(interpolated_string) => Expression::InterpolatedString(
215 format_interpolated_string(ctx, interpolated_string, shape),
216 ),
217 Expression::Symbol(token_reference) => {
218 Expression::Symbol(format_token_reference(ctx, token_reference, shape))
219 }
220 Expression::TableConstructor(table_constructor) => {
221 Expression::TableConstructor(format_table_constructor(ctx, table_constructor, shape))
222 }
223 Expression::Var(var) => Expression::Var(format_var(ctx, var, shape)),
224
225 #[cfg(feature = "luau")]
226 Expression::TypeAssertion {
227 expression,
228 type_assertion,
229 } => Expression::TypeAssertion {
230 expression: Box::new(format_expression_internal(
231 ctx,
232 expression,
233 ExpressionContext::TypeAssertion,
234 shape,
235 )),
236 type_assertion: format_type_assertion(ctx, type_assertion, shape),
237 },
238 Expression::Parentheses {
239 contained,
240 expression,
241 } => {
242 #[cfg(feature = "luau")]
243 let keep_parentheses = matches!(
244 context,
245 ExpressionContext::Prefix | ExpressionContext::TypeAssertion
246 );
247 #[cfg(not(feature = "luau"))]
248 let keep_parentheses = matches!(context, ExpressionContext::Prefix);
249
250 let use_internal_expression = check_excess_parentheses(expression, context);
253
254 if use_internal_expression && !keep_parentheses {
256 let (start_parens, end_parens) = contained.tokens();
258 let leading_comments = start_parens
259 .leading_trivia()
260 .filter(|token| trivia_util::trivia_is_comment(token))
261 .flat_map(|x| {
262 vec![
263 create_indent_trivia(ctx, shape),
264 x.to_owned(),
265 create_newline_trivia(ctx),
266 ]
267 })
268 .collect();
270
271 let trailing_comments = end_parens
272 .trailing_trivia()
273 .filter(|token| trivia_util::trivia_is_comment(token))
274 .flat_map(|x| {
275 vec![Token::new(TokenType::spaces(1)), x.to_owned()]
277 })
278 .collect();
279
280 format_expression(ctx, expression, shape)
281 .update_leading_trivia(FormatTriviaType::Append(leading_comments))
282 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments))
283 } else {
284 Expression::Parentheses {
285 contained: format_contained_span(ctx, contained, shape),
286 expression: Box::new(format_expression(ctx, expression, shape + 1)), }
288 }
289 }
290 Expression::UnaryOperator { unop, expression } => {
291 let unop = format_unop(ctx, unop, shape);
292 let shape = shape + strip_leading_trivia(&unop).to_string().len();
293 let mut expression = format_expression_internal(
294 ctx,
295 expression,
296 ExpressionContext::UnaryOrBinary,
297 shape,
298 );
299
300 if let UnOp::Minus(_) = unop {
303 let require_parentheses = match expression {
304 Expression::UnaryOperator {
305 unop: UnOp::Minus(_),
306 ..
307 } => true,
308 Expression::Parentheses { ref expression, .. } => matches!(
309 &**expression,
310 Expression::UnaryOperator {
311 unop: UnOp::Minus(_),
312 ..
313 }
314 ),
315 _ => false,
316 };
317
318 if require_parentheses {
319 let (new_expression, trailing_comments) =
320 trivia_util::take_trailing_comments(&expression);
321 expression = Expression::Parentheses {
322 contained: ContainedSpan::new(
323 TokenReference::symbol("(").unwrap(),
324 TokenReference::symbol(")").unwrap(),
325 )
326 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments)),
327 expression: Box::new(new_expression),
328 }
329 }
330 }
331
332 Expression::UnaryOperator {
333 unop,
334 expression: Box::new(expression),
335 }
336 }
337 Expression::BinaryOperator { lhs, binop, rhs } => {
338 let context = if let BinOp::Caret(_) = binop {
339 ExpressionContext::BinaryLHSExponent
340 } else {
341 ExpressionContext::BinaryLHS
342 };
343 let lhs = format_expression_internal(ctx, lhs, context, shape);
344 let binop = format_binop(ctx, binop, shape);
345 let shape = shape.take_last_line(&lhs) + binop.to_string().len();
346 Expression::BinaryOperator {
347 lhs: Box::new(lhs),
348 binop,
349 rhs: Box::new(format_expression_internal(
350 ctx,
351 rhs,
352 ExpressionContext::UnaryOrBinary,
353 shape,
354 )),
355 }
356 }
357 other => panic!("unknown node {:?}", other),
358 }
359}
360
361pub fn is_brackets_string(expression: &Expression) -> bool {
364 match expression {
365 Expression::String(token_reference) => matches!(
366 token_reference.token_type(),
367 TokenType::StringLiteral {
368 quote_type: StringLiteralQuoteType::Brackets,
369 ..
370 }
371 ),
372 Expression::Parentheses { expression, .. } => is_brackets_string(expression),
373 #[cfg(feature = "luau")]
374 Expression::TypeAssertion { expression, .. } => is_brackets_string(expression),
375 _ => false,
376 }
377}
378
379pub fn process_dot_name(
380 ctx: &Context,
381 dot: &TokenReference,
382 name: &TokenReference,
383 shape: Shape,
384) -> (TokenReference, TokenReference) {
385 let (mut dot, mut dot_comments) =
388 take_trailing_comments(&format_token_reference(ctx, dot, shape));
389 let (name, name_comments) = take_leading_comments(&format_token_reference(ctx, name, shape));
390
391 dot_comments.extend(name_comments);
392
393 if !dot_comments.is_empty() {
394 dot = prepend_newline_indent(
395 ctx,
396 &dot.update_leading_trivia(FormatTriviaType::Append(dot_comments)),
397 shape,
398 );
399 }
400
401 (dot, name)
402}
403
404pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
406 match index {
407 Index::Brackets {
408 brackets,
409 expression,
410 } => {
411 if brackets
412 .tokens()
413 .0
414 .has_trailing_comments(CommentSearch::All)
415 || contains_comments(expression)
416 || brackets.tokens().1.has_leading_comments(CommentSearch::All)
417 {
418 let (start_bracket, end_bracket) = brackets.tokens();
419
420 let indent_shape = shape.reset().increment_additional_indent();
421
422 let brackets = ContainedSpan::new(
424 fmt_symbol!(ctx, start_bracket, "[", shape).update_trailing_trivia(
425 FormatTriviaType::Append(vec![
426 create_newline_trivia(ctx),
427 create_indent_trivia(ctx, indent_shape),
428 ]),
429 ),
430 format_end_token(ctx, end_bracket, EndTokenType::IndentComments, shape)
431 .update_leading_trivia(FormatTriviaType::Append(vec![
432 create_indent_trivia(ctx, shape),
433 ])),
434 );
435
436 let expression = format_expression(ctx, expression, indent_shape)
437 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(
438 ctx,
439 )]));
440
441 Index::Brackets {
442 brackets,
443 expression,
444 }
445 } else if is_brackets_string(expression) {
446 Index::Brackets {
447 brackets: format_contained_span(ctx, brackets, shape),
448 expression: format_expression(ctx, expression, shape + 2) .update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
450 TokenType::spaces(1),
451 )]))
452 .update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
453 TokenType::spaces(1),
454 )])),
455 }
456 } else {
457 Index::Brackets {
458 brackets: format_contained_span(ctx, brackets, shape),
459 expression: format_expression(ctx, expression, shape + 1), }
461 }
462 }
463
464 Index::Dot { dot, name } => {
465 let (dot, name) = process_dot_name(ctx, dot, name, shape);
466 Index::Dot { dot, name }
467 }
468 other => panic!("unknown node {:?}", other),
469 }
470}
471
472fn is_string(expression: &Expression) -> bool {
474 match expression {
475 Expression::String(_) => true,
476 #[cfg(feature = "luau")]
477 Expression::InterpolatedString(_) => true,
478 Expression::Parentheses { expression, .. } => is_string(expression),
479 _ => false,
480 }
481}
482
483pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
485 match prefix {
486 Prefix::Expression(expression) => {
487 let singleline_format =
488 format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
489 let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
490
491 if singeline_shape.over_budget() && !is_string(expression) {
492 Prefix::Expression(Box::new(format_hanging_expression_(
493 ctx,
494 expression,
495 shape,
496 ExpressionContext::Prefix,
497 None,
498 )))
499 } else {
500 Prefix::Expression(Box::new(singleline_format))
501 }
502 }
503 Prefix::Name(token_reference) => {
504 Prefix::Name(format_token_reference(ctx, token_reference, shape))
505 }
506 other => panic!("unknown node {:?}", other),
507 }
508}
509
510pub fn format_suffix(
512 ctx: &Context,
513 suffix: &Suffix,
514 shape: Shape,
515 call_next_node: FunctionCallNextNode,
516) -> Suffix {
517 match suffix {
518 Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
519 Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
520 other => panic!("unknown node {:?}", other),
521 }
522}
523
524#[cfg(feature = "luau")]
527fn format_else_if_expression_singleline(
528 ctx: &Context,
529 else_if_expression: &ElseIfExpression,
530 shape: Shape,
531) -> ElseIfExpression {
532 let else_if_token = fmt_symbol!(ctx, else_if_expression.else_if_token(), "elseif ", shape);
533 let else_if_condition = remove_condition_parentheses(else_if_expression.condition().to_owned());
534 let else_if_condition = format_expression(ctx, &else_if_condition, shape + 7); let (then_token, expression) = format_token_expression_sequence(
536 ctx,
537 else_if_expression.then_token(),
538 else_if_expression.expression(),
539 shape.take_first_line(&else_if_condition) + 13, );
541
542 let then_token = then_token.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
544 TokenType::spaces(1),
545 )]));
546
547 ElseIfExpression::new(else_if_condition, expression)
548 .with_else_if_token(else_if_token)
549 .with_then_token(then_token)
550}
551
552#[cfg(feature = "luau")]
555fn format_token_expression_sequence(
556 ctx: &Context,
557 token: &TokenReference,
558 expression: &Expression,
559 shape: Shape,
560) -> (TokenReference, Expression) {
561 const SPACE_LEN: usize = " ".len();
562 let formatted_token = format_token_reference(ctx, token, shape);
563 let token_width = strip_trivia(&formatted_token).to_string().len();
564
565 let formatted_expression =
566 format_expression(ctx, expression, shape.add_width(token_width + SPACE_LEN));
567
568 let requires_multiline_expression = shape.take_first_line(&formatted_expression).over_budget()
569 || token.has_trailing_comments(CommentSearch::All)
570 || trivia_util::contains_comments(
571 expression.update_trailing_trivia(FormatTriviaType::Replace(vec![])),
572 ); let newline_after_token = token.has_trailing_comments(CommentSearch::Single)
575 || expression.has_leading_comments(CommentSearch::Single);
576
577 let token = match newline_after_token {
578 true => formatted_token
580 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
581 false => {
583 formatted_token.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
584 TokenType::spaces(1),
585 )]))
586 }
587 };
588
589 let expression = match requires_multiline_expression {
590 true => match newline_after_token {
591 true => {
592 let shape = shape.reset().increment_additional_indent();
593 hang_expression(ctx, expression, shape, calculate_hang_level(expression))
594 .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
595 ctx, shape,
596 )]))
597 }
598 false => hang_expression(
599 ctx,
600 expression,
601 shape.add_width(token_width + SPACE_LEN),
602 calculate_hang_level(expression),
603 ),
604 },
605 false => formatted_expression,
606 };
607
608 (token, expression)
609}
610
611#[cfg(feature = "luau")]
613fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
614 let condition = remove_condition_parentheses(if_expression.condition().to_owned());
616 let if_token = fmt_symbol!(ctx, if_expression.if_token(), "if ", shape);
617
618 let singleline_condition = format_expression(ctx, &condition, shape.with_infinite_width());
620 let then_token = fmt_symbol!(ctx, if_expression.then_token(), " then ", shape);
621 let singleline_expression = format_expression(
622 ctx,
623 if_expression.if_expression(),
624 shape.with_infinite_width(),
625 );
626 let else_ifs = if_expression
627 .else_if_expressions()
628 .map(|else_if_expressions| {
629 else_if_expressions
630 .iter()
631 .map(|else_if_expression| {
632 format_else_if_expression_singleline(
633 ctx,
634 else_if_expression,
635 shape.with_infinite_width(),
636 )
637 })
638 .collect::<Vec<_>>()
639 });
640 let else_token = fmt_symbol!(ctx, if_expression.else_token(), " else ", shape);
641 let singleline_else_expression = format_expression(
642 ctx,
643 if_expression.else_expression(),
644 shape.with_infinite_width(),
645 );
646
647 const IF_LENGTH: usize = 3; const THEN_LENGTH: usize = 6; const ELSE_LENGTH: usize = 6; let singleline_shape = (shape + IF_LENGTH + THEN_LENGTH + ELSE_LENGTH)
653 .take_first_line(&strip_trivia(&singleline_condition))
654 .take_first_line(&strip_trivia(&singleline_expression))
655 .take_first_line(&else_ifs.as_ref().map_or(String::new(), |x| {
656 x.iter().map(|x| x.to_string()).collect::<String>()
657 }))
658 .take_first_line(&strip_trivia(&singleline_else_expression));
659
660 let require_multiline_expression = singleline_shape.over_budget()
661 || if_expression
662 .if_token()
663 .has_trailing_comments(CommentSearch::All)
664 || trivia_util::contains_comments(if_expression.condition())
665 || trivia_util::contains_comments(if_expression.then_token())
666 || trivia_util::contains_comments(if_expression.if_expression())
667 || trivia_util::contains_comments(if_expression.else_token())
668 || if_expression
669 .else_if_expressions()
670 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::contains_comments))
671 || if_expression.else_expression().has_inline_comments()
672 || trivia_util::spans_multiple_lines(&singleline_condition)
673 || trivia_util::spans_multiple_lines(&singleline_expression)
674 || else_ifs
675 .as_ref()
676 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::spans_multiple_lines))
677 || trivia_util::spans_multiple_lines(&singleline_else_expression);
678
679 if require_multiline_expression {
680 let condition = hang_expression_trailing_newline(
681 ctx,
682 if_expression.condition(),
683 shape.increment_additional_indent(),
684 Some(1),
685 );
686 let hanging_shape = shape.reset().increment_additional_indent();
687
688 let (then_token, expression) = format_token_expression_sequence(
690 ctx,
691 if_expression.then_token(),
692 if_expression.if_expression(),
693 hanging_shape,
694 );
695
696 let then_token =
698 then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
699 ctx,
700 hanging_shape,
701 )]));
702
703 let else_ifs = if_expression
705 .else_if_expressions()
706 .map(|else_if_expressions| {
707 else_if_expressions
708 .iter()
709 .map(|else_if_expression| {
710 let singleline_else_if = format_else_if_expression_singleline(
711 ctx,
712 else_if_expression,
713 hanging_shape,
714 );
715 let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
716
717 if singleline_shape.over_budget()
718 || else_if_expression
719 .else_if_token()
720 .has_trailing_comments(CommentSearch::All)
721 || trivia_util::contains_comments(else_if_expression.condition())
722 || trivia_util::contains_comments(else_if_expression.then_token())
723 {
724 let else_if_token = fmt_symbol!(
725 ctx,
726 else_if_expression.else_if_token(),
727 "elseif",
728 shape
729 )
730 .update_leading_trivia(FormatTriviaType::Append(vec![
731 create_newline_trivia(ctx),
732 create_indent_trivia(ctx, hanging_shape),
733 ]));
734
735 let condiiton_shape =
736 hanging_shape.reset().increment_additional_indent();
737 let else_if_condition = hang_expression(
738 ctx,
739 &remove_condition_parentheses(
740 else_if_expression.condition().to_owned(),
741 ),
742 condiiton_shape,
743 None,
744 )
745 .update_leading_trivia(FormatTriviaType::Append(vec![
746 create_newline_trivia(ctx),
747 create_indent_trivia(ctx, condiiton_shape),
748 ]));
749
750 let hanging_shape =
751 hanging_shape.take_first_line(&else_if_condition) + 13; let (then_token, expression) = format_token_expression_sequence(
754 ctx,
755 else_if_expression.then_token(),
756 else_if_expression.expression(),
757 hanging_shape,
758 );
759
760 let then_token =
761 then_token.update_leading_trivia(FormatTriviaType::Append(vec![
762 create_newline_trivia(ctx),
763 create_indent_trivia(ctx, hanging_shape),
764 ]));
765
766 ElseIfExpression::new(else_if_condition, expression)
767 .with_else_if_token(else_if_token)
768 .with_then_token(then_token)
769 } else {
770 singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
771 vec![
772 create_newline_trivia(ctx),
773 create_indent_trivia(ctx, hanging_shape),
774 ],
775 ))
776 }
777 })
778 .collect::<Vec<_>>()
779 });
780
781 let (else_token, else_expression) = format_token_expression_sequence(
783 ctx,
784 if_expression.else_token(),
785 if_expression.else_expression(),
786 hanging_shape + 5, );
788
789 let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
791
792 IfExpression::new(condition, expression, else_expression)
793 .with_if_token(if_token)
794 .with_then_token(then_token)
795 .with_else_if(else_ifs)
796 .with_else_token(else_token)
797 } else {
798 let else_ifs = else_ifs.map(|x| {
800 x.iter()
801 .map(|x| {
802 x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
803 TokenType::spaces(1),
804 )]))
805 })
806 .collect()
807 });
808
809 IfExpression::new(
810 singleline_condition,
811 singleline_expression,
812 singleline_else_expression,
813 )
814 .with_if_token(if_token)
815 .with_then_token(then_token)
816 .with_else_if(else_ifs)
817 .with_else_token(else_token)
818 }
819}
820
821#[cfg(feature = "luau")]
822fn format_interpolated_string(
823 ctx: &Context,
824 interpolated_string: &InterpolatedString,
825 shape: Shape,
826) -> InterpolatedString {
827 let mut shape = shape;
828
829 let mut segments = Vec::new();
830 for segment in interpolated_string.segments() {
831 let literal = format_token_reference(ctx, &segment.literal, shape);
832 shape = shape + literal.to_string().len();
833
834 let mut expression = format_expression(ctx, &segment.expression, shape);
835 shape = shape.take_last_line(&expression);
836
837 if let Expression::TableConstructor { .. } = expression {
840 expression =
841 expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
842 TokenType::spaces(1),
843 )]))
844 }
845
846 segments.push(InterpolatedStringSegment {
847 literal,
848 expression,
849 })
850 }
851
852 interpolated_string
853 .to_owned()
854 .with_segments(segments)
855 .with_last_string(format_token_reference(
856 ctx,
857 interpolated_string.last_string(),
858 shape,
859 ))
860}
861
862pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
864 match var {
865 Var::Name(token_reference) => {
866 Var::Name(format_token_reference(ctx, token_reference, shape))
867 }
868 Var::Expression(var_expression) => {
869 Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
870 }
871 other => panic!("unknown node {:?}", other),
872 }
873}
874
875pub fn format_var_expression(
876 ctx: &Context,
877 var_expression: &VarExpression,
878 shape: Shape,
879) -> VarExpression {
880 let function_call = format_function_call(
883 ctx,
884 &FunctionCall::new(var_expression.prefix().clone())
885 .with_suffixes(var_expression.suffixes().cloned().collect()),
886 shape,
887 );
888 VarExpression::new(function_call.prefix().clone())
889 .with_suffixes(function_call.suffixes().cloned().collect())
890}
891
892pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
894 fmt_op!(ctx, UnOp, unop, shape, {
895 Minus = "-",
896 Not = "not ",
897 Hash = "#",
898 #[cfg(feature = "lua53")]
899 Tilde = "~",
900 }, |other| panic!("unknown node {:?}", other))
901}
902
903fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
908 let mut leading_comments = binop
912 .leading_comments()
913 .iter()
914 .flat_map(|x| {
915 vec![
916 create_newline_trivia(ctx),
917 create_indent_trivia(ctx, shape),
918 x.to_owned(),
919 ]
920 })
921 .collect::<Vec<_>>();
922
923 let mut trailing_comments = binop.trailing_comments();
925 leading_comments.append(&mut trailing_comments);
926
927 let mut expression_leading_comments = rhs
929 .leading_comments()
930 .iter()
931 .flat_map(|x| {
932 vec![
933 create_newline_trivia(ctx),
934 create_indent_trivia(ctx, shape),
935 x.to_owned(),
936 ]
937 })
938 .collect::<Vec<_>>();
939 leading_comments.append(&mut expression_leading_comments);
940
941 leading_comments.push(create_newline_trivia(ctx));
943 leading_comments.push(create_indent_trivia(ctx, shape));
944
945 binop.update_trivia(
946 FormatTriviaType::Replace(leading_comments),
947 FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
948 )
949}
950
951fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
953 match expression {
954 Expression::BinaryOperator { lhs, binop, rhs } => {
955 if binop.precedence() >= top_binop.precedence()
956 && binop.is_right_associative() == top_binop.is_right_associative()
957 {
958 if binop.is_right_associative() {
959 binop_expression_length(rhs, top_binop)
960 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**lhs).to_string().len()
962 } else {
963 binop_expression_length(lhs, top_binop)
964 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**rhs).to_string().len()
966 }
967 } else {
968 0
969 }
970 }
971 _ => strip_trivia(expression).to_string().len(),
972 }
973}
974
975fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
976 match expression {
977 Expression::BinaryOperator { lhs, binop, rhs } => {
978 if binop.precedence() == top_binop.precedence() {
979 contains_comments(binop)
980 || rhs.has_leading_comments(CommentSearch::All)
981 || lhs.has_trailing_comments(CommentSearch::All)
982 || binop_expression_contains_comments(lhs, top_binop)
983 || binop_expression_contains_comments(rhs, top_binop)
984 } else {
985 false
986 }
987 }
988 _ => false,
989 }
990}
991
992trait ToRange {
994 fn to_range(&self) -> (usize, usize);
995}
996
997impl ToRange for (usize, usize) {
998 fn to_range(&self) -> (usize, usize) {
999 *self
1000 }
1001}
1002
1003impl ToRange for Expression {
1004 fn to_range(&self) -> (usize, usize) {
1005 let (start, end) = self.range().unwrap();
1006 (start.bytes(), end.bytes())
1007 }
1008}
1009
1010#[derive(Clone, Copy, Debug)]
1036struct LeftmostRangeHang {
1037 range: (usize, usize),
1038 original_additional_indent_level: usize,
1039}
1040
1041impl LeftmostRangeHang {
1042 fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1045 match expression {
1046 Expression::BinaryOperator { lhs, .. } => {
1047 Self::find(lhs, original_additional_indent_level)
1048 }
1049 _ => Self {
1050 range: expression.to_range(),
1051 original_additional_indent_level,
1052 },
1053 }
1054 }
1055
1056 fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1060 let (expression_start, expression_end) = item.to_range();
1061 let (lhs_start, lhs_end) = self.range;
1062
1063 if lhs_start >= expression_start && lhs_end <= expression_end {
1064 shape.with_indent(
1065 shape
1066 .indent()
1067 .with_additional_indent(self.original_additional_indent_level),
1068 )
1069 } else {
1070 shape
1071 }
1072 }
1073}
1074
1075fn is_hang_binop_over_width(
1076 shape: Shape,
1077 expression: &Expression,
1078 top_binop: &BinOp,
1079 lhs_range: Option<LeftmostRangeHang>,
1080) -> bool {
1081 let shape = if let Some(lhs_hang) = lhs_range {
1082 lhs_hang.required_shape(shape, expression)
1083 } else {
1084 shape
1085 };
1086
1087 shape
1088 .add_width(binop_expression_length(expression, top_binop))
1089 .over_budget()
1090}
1091
1092fn binop_precedence_level(expression: &Expression) -> u8 {
1094 match expression {
1095 Expression::BinaryOperator { binop, .. } => binop.precedence(),
1096 _ => 0,
1097 }
1098}
1099
1100fn did_hang_expression(expression: &Expression) -> bool {
1101 if let Expression::BinaryOperator { binop, .. } = expression {
1102 binop
1105 .surrounding_trivia()
1106 .0
1107 .iter()
1108 .any(|x| trivia_is_newline(x))
1109 } else {
1110 false
1111 }
1112}
1113
1114#[derive(Debug)]
1115enum ExpressionSide {
1116 Left,
1117 Right,
1118}
1119
1120fn hang_binop_expression(
1121 ctx: &Context,
1122 expression: Expression,
1123 top_binop: BinOp,
1124 shape: Shape,
1125 lhs_range: Option<LeftmostRangeHang>,
1126 expression_context: ExpressionContext,
1127) -> Expression {
1128 const SPACE_LEN: usize = " ".len();
1129
1130 let full_expression = expression.to_owned();
1131
1132 match expression {
1133 Expression::BinaryOperator { lhs, binop, rhs } => {
1134 let same_op_level = binop.precedence() == top_binop.precedence()
1137 && binop.is_right_associative() == top_binop.is_right_associative();
1138 let is_right_associative = binop.is_right_associative();
1139
1140 let test_shape = if same_op_level {
1141 shape
1142 } else {
1143 shape.increment_additional_indent()
1144 };
1145
1146 let side_to_hang = if is_right_associative {
1147 ExpressionSide::Right
1148 } else {
1149 ExpressionSide::Left
1150 };
1151
1152 let over_column_width =
1154 is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
1155 let should_hang = same_op_level
1156 || over_column_width
1157 || binop_expression_contains_comments(&full_expression, &binop);
1158
1159 let shape = if should_hang { test_shape } else { shape };
1161
1162 let mut new_binop = format_binop(ctx, &binop, shape);
1163 if should_hang {
1164 new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
1165 }
1166
1167 let (lhs, rhs) = match should_hang {
1168 true => {
1169 let lhs_shape = shape;
1170 let rhs_shape =
1171 shape.reset() + strip_trivia(&new_binop).to_string().len() + SPACE_LEN;
1172
1173 let (lhs, rhs) = match side_to_hang {
1174 ExpressionSide::Left => (
1175 hang_binop_expression(
1176 ctx,
1177 *lhs,
1178 if same_op_level {
1179 top_binop
1180 } else {
1181 binop.clone()
1182 },
1183 lhs_shape,
1184 lhs_range,
1185 expression_context,
1186 ),
1187 if contains_comments(&*rhs) {
1188 hang_binop_expression(
1189 ctx,
1190 *rhs,
1191 binop,
1192 shape,
1193 lhs_range,
1194 expression_context,
1195 )
1196 } else {
1197 format_expression_internal(
1198 ctx,
1199 &rhs,
1200 ExpressionContext::UnaryOrBinary,
1201 rhs_shape,
1202 )
1203 },
1204 ),
1205 ExpressionSide::Right => (
1206 if contains_comments(&*lhs) {
1207 hang_binop_expression(
1208 ctx,
1209 *lhs,
1210 binop.clone(),
1211 shape,
1212 lhs_range,
1213 expression_context,
1214 )
1215 } else {
1216 let context = if let BinOp::Caret(_) = binop {
1217 ExpressionContext::BinaryLHSExponent
1218 } else {
1219 ExpressionContext::BinaryLHS
1220 };
1221 format_expression_internal(ctx, &lhs, context, lhs_shape)
1222 },
1223 hang_binop_expression(
1224 ctx,
1225 *rhs,
1226 if same_op_level { top_binop } else { binop },
1227 rhs_shape,
1228 lhs_range,
1229 expression_context,
1230 ),
1231 ),
1232 };
1233 (
1234 lhs,
1235 rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1236 )
1237 }
1238 false => {
1239 let lhs = if contains_comments(&*lhs) {
1242 hang_binop_expression(
1243 ctx,
1244 *lhs,
1245 binop.to_owned(),
1246 shape,
1247 lhs_range,
1248 expression_context,
1249 )
1250 } else {
1251 let context = if let BinOp::Caret(_) = binop {
1252 ExpressionContext::BinaryLHSExponent
1253 } else {
1254 ExpressionContext::BinaryLHS
1255 };
1256 format_expression_internal(ctx, &lhs, context, shape)
1257 };
1258
1259 let rhs = if contains_comments(&*rhs) {
1260 hang_binop_expression(
1261 ctx,
1262 *rhs,
1263 binop,
1264 shape,
1265 lhs_range,
1266 expression_context,
1267 )
1268 } else {
1269 format_expression_internal(
1270 ctx,
1271 &rhs,
1272 ExpressionContext::UnaryOrBinary,
1273 shape,
1274 )
1275 };
1276
1277 (lhs, rhs)
1278 }
1279 };
1280
1281 Expression::BinaryOperator {
1282 lhs: Box::new(lhs),
1283 binop: new_binop,
1284 rhs: Box::new(rhs),
1285 }
1286 }
1287 _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1289 }
1290}
1291
1292fn format_hanging_expression_(
1294 ctx: &Context,
1295 expression: &Expression,
1296 shape: Shape,
1297 expression_context: ExpressionContext,
1298 lhs_range: Option<LeftmostRangeHang>,
1299) -> Expression {
1300 let expression_range = expression.to_range();
1301
1302 match expression {
1303 #[cfg(feature = "luau")]
1304 Expression::TypeAssertion {
1305 expression,
1306 type_assertion,
1307 } => {
1308 let (expression_context, value_shape) = (
1311 ExpressionContext::TypeAssertion,
1312 shape.take_first_line(&strip_trivia(type_assertion)),
1313 );
1314
1315 let expression = format_hanging_expression_(
1316 ctx,
1317 expression,
1318 value_shape,
1319 expression_context,
1320 lhs_range,
1321 );
1322
1323 #[cfg(feature = "luau")]
1325 let assertion_shape = shape.take_last_line(&expression);
1326
1327 Expression::TypeAssertion {
1328 expression: Box::new(expression),
1329 type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1330 }
1331 }
1332 Expression::Parentheses {
1333 contained,
1334 expression,
1335 } => {
1336 let lhs_shape = if let Some(lhs_hang) = lhs_range {
1337 lhs_hang.required_shape(shape, &expression_range)
1338 } else {
1339 shape
1340 };
1341 #[cfg(feature = "luau")]
1342 let keep_parentheses = matches!(
1343 expression_context,
1344 ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1345 );
1346 #[cfg(not(feature = "luau"))]
1347 let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1348
1349 let use_internal_expression = check_excess_parentheses(expression, expression_context);
1352
1353 if use_internal_expression && !keep_parentheses {
1355 format_hanging_expression_(
1356 ctx,
1357 expression,
1358 lhs_shape,
1359 expression_context,
1360 lhs_range,
1361 )
1362 } else {
1363 let contained = format_contained_span(ctx, contained, lhs_shape);
1364
1365 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); let expression_str = formatted_expression.to_string();
1370 if !contains_comments(expression)
1371 && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1372 {
1373 return Expression::Parentheses {
1375 contained,
1376 expression: Box::new(formatted_expression),
1377 };
1378 }
1379
1380 let expression_shape = lhs_shape.reset().increment_additional_indent();
1382
1383 let (start_token, end_token) = contained.tokens();
1385
1386 let contained = ContainedSpan::new(
1389 start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1390 create_newline_trivia(ctx),
1391 create_indent_trivia(ctx, expression_shape),
1392 ])),
1393 end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1394 create_newline_trivia(ctx),
1395 create_indent_trivia(ctx, lhs_shape),
1396 ])),
1397 );
1398
1399 Expression::Parentheses {
1400 contained,
1401 expression: Box::new(format_hanging_expression_(
1402 ctx,
1403 expression,
1404 expression_shape,
1405 ExpressionContext::Standard,
1406 None,
1407 )),
1408 }
1409 }
1410 }
1411 Expression::UnaryOperator { unop, expression } => {
1412 let unop = format_unop(ctx, unop, shape);
1413 let shape = shape + strip_leading_trivia(&unop).to_string().len();
1414 let expression = format_hanging_expression_(
1415 ctx,
1416 expression,
1417 shape,
1418 ExpressionContext::UnaryOrBinary,
1419 lhs_range,
1420 );
1421
1422 Expression::UnaryOperator {
1423 unop,
1424 expression: Box::new(expression),
1425 }
1426 }
1427 Expression::BinaryOperator { lhs, binop, rhs } => {
1428 let lhs = hang_binop_expression(
1430 ctx,
1431 *lhs.to_owned(),
1432 binop.to_owned(),
1433 shape,
1434 lhs_range,
1435 ExpressionContext::UnaryOrBinary,
1436 );
1437
1438 let current_shape = shape.take_last_line(&lhs) + 1; let mut new_binop = format_binop(ctx, binop, current_shape);
1440
1441 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; let mut new_rhs = hang_binop_expression(
1444 ctx,
1445 *rhs.to_owned(),
1446 binop.to_owned(),
1447 singleline_shape,
1448 None,
1449 ExpressionContext::Standard,
1450 );
1451
1452 if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1454 || (did_hang_expression(&new_rhs)
1455 && binop_precedence_level(&new_rhs) >= binop.precedence())
1456 || contains_comments(binop)
1457 || lhs.has_trailing_comments(CommentSearch::All)
1458 || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1459 {
1460 let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1461 new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1462 new_rhs = hang_binop_expression(
1463 ctx,
1464 *rhs.to_owned(),
1465 binop.to_owned(),
1466 hanging_shape,
1467 None,
1468 ExpressionContext::Standard,
1469 )
1470 .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1471 }
1472
1473 Expression::BinaryOperator {
1474 lhs: Box::new(lhs),
1475 binop: new_binop,
1476 rhs: Box::new(new_rhs),
1477 }
1478 }
1479 _ => {
1480 let value_shape = if let Some(lhs_hang) = lhs_range {
1481 lhs_hang.required_shape(shape, &expression_range)
1482 } else {
1483 shape
1484 };
1485
1486 format_expression_internal(ctx, expression, expression_context, value_shape)
1487 }
1488 }
1489}
1490
1491pub fn hang_expression(
1492 ctx: &Context,
1493 expression: &Expression,
1494 shape: Shape,
1495 hang_level: Option<usize>,
1496) -> Expression {
1497 let original_additional_indent_level = shape.indent().additional_indent();
1498 let shape = match hang_level {
1499 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1500 None => shape,
1501 };
1502
1503 let lhs_range =
1504 hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1505
1506 format_hanging_expression_(
1507 ctx,
1508 expression,
1509 shape,
1510 ExpressionContext::Standard,
1511 lhs_range,
1512 )
1513}
1514
1515pub fn hang_expression_trailing_newline(
1516 ctx: &Context,
1517 expression: &Expression,
1518 shape: Shape,
1519 hang_level: Option<usize>,
1520) -> Expression {
1521 hang_expression(ctx, expression, shape, hang_level)
1522 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1523}