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 #[cfg(feature = "luau")]
373 Expression::TypeAssertion { expression, .. } => is_brackets_string(expression),
374 _ => false,
375 }
376}
377
378pub fn process_dot_name(
379 ctx: &Context,
380 dot: &TokenReference,
381 name: &TokenReference,
382 shape: Shape,
383) -> (TokenReference, TokenReference) {
384 let (mut dot, mut dot_comments) =
387 take_trailing_comments(&format_token_reference(ctx, dot, shape));
388 let (name, name_comments) = take_leading_comments(&format_token_reference(ctx, name, shape));
389
390 dot_comments.extend(name_comments);
391
392 if !dot_comments.is_empty() {
393 dot = prepend_newline_indent(
394 ctx,
395 &dot.update_leading_trivia(FormatTriviaType::Append(dot_comments)),
396 shape,
397 );
398 }
399
400 (dot, name)
401}
402
403pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
405 match index {
406 Index::Brackets {
407 brackets,
408 expression,
409 } => {
410 if brackets
411 .tokens()
412 .0
413 .has_trailing_comments(CommentSearch::All)
414 || contains_comments(expression)
415 || brackets.tokens().1.has_leading_comments(CommentSearch::All)
416 {
417 let (start_bracket, end_bracket) = brackets.tokens();
418
419 let indent_shape = shape.reset().increment_additional_indent();
420
421 let brackets = ContainedSpan::new(
423 fmt_symbol!(ctx, start_bracket, "[", shape).update_trailing_trivia(
424 FormatTriviaType::Append(vec![
425 create_newline_trivia(ctx),
426 create_indent_trivia(ctx, indent_shape),
427 ]),
428 ),
429 format_end_token(ctx, end_bracket, EndTokenType::IndentComments, shape)
430 .update_leading_trivia(FormatTriviaType::Append(vec![
431 create_indent_trivia(ctx, shape),
432 ])),
433 );
434
435 let expression = format_expression(ctx, expression, indent_shape)
436 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(
437 ctx,
438 )]));
439
440 Index::Brackets {
441 brackets,
442 expression,
443 }
444 } else if is_brackets_string(expression) {
445 Index::Brackets {
446 brackets: format_contained_span(ctx, brackets, shape),
447 expression: format_expression(ctx, expression, shape + 2) .update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
449 TokenType::spaces(1),
450 )]))
451 .update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
452 TokenType::spaces(1),
453 )])),
454 }
455 } else {
456 Index::Brackets {
457 brackets: format_contained_span(ctx, brackets, shape),
458 expression: format_expression(ctx, expression, shape + 1), }
460 }
461 }
462
463 Index::Dot { dot, name } => {
464 let (dot, name) = process_dot_name(ctx, dot, name, shape);
465 Index::Dot { dot, name }
466 }
467 other => panic!("unknown node {:?}", other),
468 }
469}
470
471fn is_string(expression: &Expression) -> bool {
473 match expression {
474 Expression::String(_) => true,
475 #[cfg(feature = "luau")]
476 Expression::InterpolatedString(_) => true,
477 Expression::Parentheses { expression, .. } => is_string(expression),
478 _ => false,
479 }
480}
481
482pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
484 match prefix {
485 Prefix::Expression(expression) => {
486 let singleline_format =
487 format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
488 let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
489
490 if singeline_shape.over_budget() && !is_string(expression) {
491 Prefix::Expression(Box::new(format_hanging_expression_(
492 ctx,
493 expression,
494 shape,
495 ExpressionContext::Prefix,
496 None,
497 )))
498 } else {
499 Prefix::Expression(Box::new(singleline_format))
500 }
501 }
502 Prefix::Name(token_reference) => {
503 Prefix::Name(format_token_reference(ctx, token_reference, shape))
504 }
505 other => panic!("unknown node {:?}", other),
506 }
507}
508
509pub fn format_suffix(
511 ctx: &Context,
512 suffix: &Suffix,
513 shape: Shape,
514 call_next_node: FunctionCallNextNode,
515) -> Suffix {
516 match suffix {
517 Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
518 Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
519 other => panic!("unknown node {:?}", other),
520 }
521}
522
523#[cfg(feature = "luau")]
526fn format_else_if_expression_singleline(
527 ctx: &Context,
528 else_if_expression: &ElseIfExpression,
529 shape: Shape,
530) -> ElseIfExpression {
531 let else_if_token = fmt_symbol!(ctx, else_if_expression.else_if_token(), "elseif ", shape);
532 let else_if_condition = remove_condition_parentheses(else_if_expression.condition().to_owned());
533 let else_if_condition = format_expression(ctx, &else_if_condition, shape + 7); let (then_token, expression) = format_token_expression_sequence(
535 ctx,
536 else_if_expression.then_token(),
537 else_if_expression.expression(),
538 shape.take_first_line(&else_if_condition) + 13, );
540
541 let then_token = then_token.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
543 TokenType::spaces(1),
544 )]));
545
546 ElseIfExpression::new(else_if_condition, expression)
547 .with_else_if_token(else_if_token)
548 .with_then_token(then_token)
549}
550
551#[cfg(feature = "luau")]
554fn format_token_expression_sequence(
555 ctx: &Context,
556 token: &TokenReference,
557 expression: &Expression,
558 shape: Shape,
559) -> (TokenReference, Expression) {
560 const SPACE_LEN: usize = " ".len();
561 let formatted_token = format_token_reference(ctx, token, shape);
562 let token_width = strip_trivia(&formatted_token).to_string().len();
563
564 let formatted_expression =
565 format_expression(ctx, expression, shape.add_width(token_width + SPACE_LEN));
566
567 let requires_multiline_expression = shape.take_first_line(&formatted_expression).over_budget()
568 || token.has_trailing_comments(CommentSearch::All)
569 || trivia_util::contains_comments(
570 expression.update_trailing_trivia(FormatTriviaType::Replace(vec![])),
571 ); let newline_after_token = token.has_trailing_comments(CommentSearch::Single)
574 || expression.has_leading_comments(CommentSearch::Single);
575
576 let token = match newline_after_token {
577 true => formatted_token
579 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
580 false => {
582 formatted_token.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
583 TokenType::spaces(1),
584 )]))
585 }
586 };
587
588 let expression = match requires_multiline_expression {
589 true => match newline_after_token {
590 true => {
591 let shape = shape.reset().increment_additional_indent();
592 hang_expression(ctx, expression, shape, calculate_hang_level(expression))
593 .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
594 ctx, shape,
595 )]))
596 }
597 false => hang_expression(
598 ctx,
599 expression,
600 shape.add_width(token_width + SPACE_LEN),
601 calculate_hang_level(expression),
602 ),
603 },
604 false => formatted_expression,
605 };
606
607 (token, expression)
608}
609
610#[cfg(feature = "luau")]
612fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
613 let condition = remove_condition_parentheses(if_expression.condition().to_owned());
615 let if_token = fmt_symbol!(ctx, if_expression.if_token(), "if ", shape);
616
617 let singleline_condition = format_expression(ctx, &condition, shape.with_infinite_width());
619 let then_token = fmt_symbol!(ctx, if_expression.then_token(), " then ", shape);
620 let singleline_expression = format_expression(
621 ctx,
622 if_expression.if_expression(),
623 shape.with_infinite_width(),
624 );
625 let else_ifs = if_expression
626 .else_if_expressions()
627 .map(|else_if_expressions| {
628 else_if_expressions
629 .iter()
630 .map(|else_if_expression| {
631 format_else_if_expression_singleline(
632 ctx,
633 else_if_expression,
634 shape.with_infinite_width(),
635 )
636 })
637 .collect::<Vec<_>>()
638 });
639 let else_token = fmt_symbol!(ctx, if_expression.else_token(), " else ", shape);
640 let singleline_else_expression = format_expression(
641 ctx,
642 if_expression.else_expression(),
643 shape.with_infinite_width(),
644 );
645
646 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)
652 .take_first_line(&strip_trivia(&singleline_condition))
653 .take_first_line(&strip_trivia(&singleline_expression))
654 .take_first_line(&else_ifs.as_ref().map_or(String::new(), |x| {
655 x.iter().map(|x| x.to_string()).collect::<String>()
656 }))
657 .take_first_line(&strip_trivia(&singleline_else_expression));
658
659 let require_multiline_expression = singleline_shape.over_budget()
660 || if_expression
661 .if_token()
662 .has_trailing_comments(CommentSearch::All)
663 || trivia_util::contains_comments(if_expression.condition())
664 || trivia_util::contains_comments(if_expression.then_token())
665 || trivia_util::contains_comments(if_expression.if_expression())
666 || trivia_util::contains_comments(if_expression.else_token())
667 || if_expression
668 .else_if_expressions()
669 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::contains_comments))
670 || if_expression.else_expression().has_inline_comments()
671 || trivia_util::spans_multiple_lines(&singleline_condition)
672 || trivia_util::spans_multiple_lines(&singleline_expression)
673 || else_ifs
674 .as_ref()
675 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::spans_multiple_lines))
676 || trivia_util::spans_multiple_lines(&singleline_else_expression);
677
678 if require_multiline_expression {
679 let condition = hang_expression_trailing_newline(
680 ctx,
681 if_expression.condition(),
682 shape.increment_additional_indent(),
683 Some(1),
684 );
685 let hanging_shape = shape.reset().increment_additional_indent();
686
687 let (then_token, expression) = format_token_expression_sequence(
689 ctx,
690 if_expression.then_token(),
691 if_expression.if_expression(),
692 hanging_shape,
693 );
694
695 let then_token =
697 then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
698 ctx,
699 hanging_shape,
700 )]));
701
702 let else_ifs = if_expression
704 .else_if_expressions()
705 .map(|else_if_expressions| {
706 else_if_expressions
707 .iter()
708 .map(|else_if_expression| {
709 let singleline_else_if = format_else_if_expression_singleline(
710 ctx,
711 else_if_expression,
712 hanging_shape,
713 );
714 let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
715
716 if singleline_shape.over_budget()
717 || else_if_expression
718 .else_if_token()
719 .has_trailing_comments(CommentSearch::All)
720 || trivia_util::contains_comments(else_if_expression.condition())
721 || trivia_util::contains_comments(else_if_expression.then_token())
722 {
723 let else_if_token = fmt_symbol!(
724 ctx,
725 else_if_expression.else_if_token(),
726 "elseif",
727 shape
728 )
729 .update_leading_trivia(FormatTriviaType::Append(vec![
730 create_newline_trivia(ctx),
731 create_indent_trivia(ctx, hanging_shape),
732 ]));
733
734 let condiiton_shape =
735 hanging_shape.reset().increment_additional_indent();
736 let else_if_condition = hang_expression(
737 ctx,
738 &remove_condition_parentheses(
739 else_if_expression.condition().to_owned(),
740 ),
741 condiiton_shape,
742 None,
743 )
744 .update_leading_trivia(FormatTriviaType::Append(vec![
745 create_newline_trivia(ctx),
746 create_indent_trivia(ctx, condiiton_shape),
747 ]));
748
749 let hanging_shape =
750 hanging_shape.take_first_line(&else_if_condition) + 13; let (then_token, expression) = format_token_expression_sequence(
753 ctx,
754 else_if_expression.then_token(),
755 else_if_expression.expression(),
756 hanging_shape,
757 );
758
759 let then_token =
760 then_token.update_leading_trivia(FormatTriviaType::Append(vec![
761 create_newline_trivia(ctx),
762 create_indent_trivia(ctx, hanging_shape),
763 ]));
764
765 ElseIfExpression::new(else_if_condition, expression)
766 .with_else_if_token(else_if_token)
767 .with_then_token(then_token)
768 } else {
769 singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
770 vec![
771 create_newline_trivia(ctx),
772 create_indent_trivia(ctx, hanging_shape),
773 ],
774 ))
775 }
776 })
777 .collect::<Vec<_>>()
778 });
779
780 let (else_token, else_expression) = format_token_expression_sequence(
782 ctx,
783 if_expression.else_token(),
784 if_expression.else_expression(),
785 hanging_shape + 5, );
787
788 let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
790
791 IfExpression::new(condition, expression, else_expression)
792 .with_if_token(if_token)
793 .with_then_token(then_token)
794 .with_else_if(else_ifs)
795 .with_else_token(else_token)
796 } else {
797 let else_ifs = else_ifs.map(|x| {
799 x.iter()
800 .map(|x| {
801 x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
802 TokenType::spaces(1),
803 )]))
804 })
805 .collect()
806 });
807
808 IfExpression::new(
809 singleline_condition,
810 singleline_expression,
811 singleline_else_expression,
812 )
813 .with_if_token(if_token)
814 .with_then_token(then_token)
815 .with_else_if(else_ifs)
816 .with_else_token(else_token)
817 }
818}
819
820#[cfg(feature = "luau")]
821fn format_interpolated_string(
822 ctx: &Context,
823 interpolated_string: &InterpolatedString,
824 shape: Shape,
825) -> InterpolatedString {
826 let mut shape = shape;
827
828 let mut segments = Vec::new();
829 for segment in interpolated_string.segments() {
830 let literal = format_token_reference(ctx, &segment.literal, shape);
831 shape = shape + literal.to_string().len();
832
833 let mut expression = format_expression(ctx, &segment.expression, shape);
834 shape = shape.take_last_line(&expression);
835
836 if let Expression::TableConstructor { .. } = expression {
839 expression =
840 expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
841 TokenType::spaces(1),
842 )]))
843 }
844
845 segments.push(InterpolatedStringSegment {
846 literal,
847 expression,
848 })
849 }
850
851 interpolated_string
852 .to_owned()
853 .with_segments(segments)
854 .with_last_string(format_token_reference(
855 ctx,
856 interpolated_string.last_string(),
857 shape,
858 ))
859}
860
861pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
863 match var {
864 Var::Name(token_reference) => {
865 Var::Name(format_token_reference(ctx, token_reference, shape))
866 }
867 Var::Expression(var_expression) => {
868 Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
869 }
870 other => panic!("unknown node {:?}", other),
871 }
872}
873
874pub fn format_var_expression(
875 ctx: &Context,
876 var_expression: &VarExpression,
877 shape: Shape,
878) -> VarExpression {
879 let function_call = format_function_call(
882 ctx,
883 &FunctionCall::new(var_expression.prefix().clone())
884 .with_suffixes(var_expression.suffixes().cloned().collect()),
885 shape,
886 );
887 VarExpression::new(function_call.prefix().clone())
888 .with_suffixes(function_call.suffixes().cloned().collect())
889}
890
891pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
893 fmt_op!(ctx, UnOp, unop, shape, {
894 Minus = "-",
895 Not = "not ",
896 Hash = "#",
897 #[cfg(feature = "lua53")]
898 Tilde = "~",
899 }, |other| panic!("unknown node {:?}", other))
900}
901
902fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
907 let mut leading_comments = binop
911 .leading_comments()
912 .iter()
913 .flat_map(|x| {
914 vec![
915 create_newline_trivia(ctx),
916 create_indent_trivia(ctx, shape),
917 x.to_owned(),
918 ]
919 })
920 .collect::<Vec<_>>();
921
922 let mut trailing_comments = binop.trailing_comments();
924 leading_comments.append(&mut trailing_comments);
925
926 let mut expression_leading_comments = rhs
928 .leading_comments()
929 .iter()
930 .flat_map(|x| {
931 vec![
932 create_newline_trivia(ctx),
933 create_indent_trivia(ctx, shape),
934 x.to_owned(),
935 ]
936 })
937 .collect::<Vec<_>>();
938 leading_comments.append(&mut expression_leading_comments);
939
940 leading_comments.push(create_newline_trivia(ctx));
942 leading_comments.push(create_indent_trivia(ctx, shape));
943
944 binop.update_trivia(
945 FormatTriviaType::Replace(leading_comments),
946 FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
947 )
948}
949
950fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
952 match expression {
953 Expression::BinaryOperator { lhs, binop, rhs } => {
954 if binop.precedence() >= top_binop.precedence()
955 && binop.is_right_associative() == top_binop.is_right_associative()
956 {
957 if binop.is_right_associative() {
958 binop_expression_length(rhs, top_binop)
959 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**lhs).to_string().len()
961 } else {
962 binop_expression_length(lhs, top_binop)
963 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**rhs).to_string().len()
965 }
966 } else {
967 0
968 }
969 }
970 _ => strip_trivia(expression).to_string().len(),
971 }
972}
973
974fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
975 match expression {
976 Expression::BinaryOperator { lhs, binop, rhs } => {
977 if binop.precedence() == top_binop.precedence() {
978 contains_comments(binop)
979 || rhs.has_leading_comments(CommentSearch::All)
980 || lhs.has_trailing_comments(CommentSearch::All)
981 || binop_expression_contains_comments(lhs, top_binop)
982 || binop_expression_contains_comments(rhs, top_binop)
983 } else {
984 false
985 }
986 }
987 _ => false,
988 }
989}
990
991trait ToRange {
993 fn to_range(&self) -> (usize, usize);
994}
995
996impl ToRange for (usize, usize) {
997 fn to_range(&self) -> (usize, usize) {
998 *self
999 }
1000}
1001
1002impl ToRange for Expression {
1003 fn to_range(&self) -> (usize, usize) {
1004 let (start, end) = self.range().unwrap();
1005 (start.bytes(), end.bytes())
1006 }
1007}
1008
1009#[derive(Clone, Copy, Debug)]
1035struct LeftmostRangeHang {
1036 range: (usize, usize),
1037 original_additional_indent_level: usize,
1038}
1039
1040impl LeftmostRangeHang {
1041 fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1044 match expression {
1045 Expression::BinaryOperator { lhs, .. } => {
1046 Self::find(lhs, original_additional_indent_level)
1047 }
1048 _ => Self {
1049 range: expression.to_range(),
1050 original_additional_indent_level,
1051 },
1052 }
1053 }
1054
1055 fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1059 let (expression_start, expression_end) = item.to_range();
1060 let (lhs_start, lhs_end) = self.range;
1061
1062 if lhs_start >= expression_start && lhs_end <= expression_end {
1063 shape.with_indent(
1064 shape
1065 .indent()
1066 .with_additional_indent(self.original_additional_indent_level),
1067 )
1068 } else {
1069 shape
1070 }
1071 }
1072}
1073
1074fn is_hang_binop_over_width(
1075 shape: Shape,
1076 expression: &Expression,
1077 top_binop: &BinOp,
1078 lhs_range: Option<LeftmostRangeHang>,
1079) -> bool {
1080 let shape = if let Some(lhs_hang) = lhs_range {
1081 lhs_hang.required_shape(shape, expression)
1082 } else {
1083 shape
1084 };
1085
1086 shape
1087 .add_width(binop_expression_length(expression, top_binop))
1088 .over_budget()
1089}
1090
1091fn binop_precedence_level(expression: &Expression) -> u8 {
1093 match expression {
1094 Expression::BinaryOperator { binop, .. } => binop.precedence(),
1095 _ => 0,
1096 }
1097}
1098
1099fn did_hang_expression(expression: &Expression) -> bool {
1100 if let Expression::BinaryOperator { binop, .. } = expression {
1101 binop
1104 .surrounding_trivia()
1105 .0
1106 .iter()
1107 .any(|x| trivia_is_newline(x))
1108 } else {
1109 false
1110 }
1111}
1112
1113#[derive(Debug)]
1114enum ExpressionSide {
1115 Left,
1116 Right,
1117}
1118
1119fn hang_binop_expression(
1120 ctx: &Context,
1121 expression: Expression,
1122 top_binop: BinOp,
1123 shape: Shape,
1124 lhs_range: Option<LeftmostRangeHang>,
1125 expression_context: ExpressionContext,
1126) -> Expression {
1127 const SPACE_LEN: usize = " ".len();
1128
1129 let full_expression = expression.to_owned();
1130
1131 match expression {
1132 Expression::BinaryOperator { lhs, binop, rhs } => {
1133 let same_op_level = binop.precedence() == top_binop.precedence()
1136 && binop.is_right_associative() == top_binop.is_right_associative();
1137 let is_right_associative = binop.is_right_associative();
1138
1139 let test_shape = if same_op_level {
1140 shape
1141 } else {
1142 shape.increment_additional_indent()
1143 };
1144
1145 let side_to_hang = if is_right_associative {
1146 ExpressionSide::Right
1147 } else {
1148 ExpressionSide::Left
1149 };
1150
1151 let over_column_width =
1153 is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
1154 let should_hang = same_op_level
1155 || over_column_width
1156 || binop_expression_contains_comments(&full_expression, &binop);
1157
1158 let shape = if should_hang { test_shape } else { shape };
1160
1161 let mut new_binop = format_binop(ctx, &binop, shape);
1162 if should_hang {
1163 new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
1164 }
1165
1166 let (lhs, rhs) = match should_hang {
1167 true => {
1168 let lhs_shape = shape;
1169 let rhs_shape =
1170 shape.reset() + strip_trivia(&new_binop).to_string().len() + SPACE_LEN;
1171
1172 let (lhs, rhs) = match side_to_hang {
1173 ExpressionSide::Left => (
1174 hang_binop_expression(
1175 ctx,
1176 *lhs,
1177 if same_op_level {
1178 top_binop
1179 } else {
1180 binop.clone()
1181 },
1182 lhs_shape,
1183 lhs_range,
1184 expression_context,
1185 ),
1186 if contains_comments(&*rhs) {
1187 hang_binop_expression(
1188 ctx,
1189 *rhs,
1190 binop,
1191 shape,
1192 lhs_range,
1193 expression_context,
1194 )
1195 } else {
1196 format_expression_internal(
1197 ctx,
1198 &rhs,
1199 ExpressionContext::UnaryOrBinary,
1200 rhs_shape,
1201 )
1202 },
1203 ),
1204 ExpressionSide::Right => (
1205 if contains_comments(&*lhs) {
1206 hang_binop_expression(
1207 ctx,
1208 *lhs,
1209 binop.clone(),
1210 shape,
1211 lhs_range,
1212 expression_context,
1213 )
1214 } else {
1215 let context = if let BinOp::Caret(_) = binop {
1216 ExpressionContext::BinaryLHSExponent
1217 } else {
1218 ExpressionContext::BinaryLHS
1219 };
1220 format_expression_internal(ctx, &lhs, context, lhs_shape)
1221 },
1222 hang_binop_expression(
1223 ctx,
1224 *rhs,
1225 if same_op_level { top_binop } else { binop },
1226 rhs_shape,
1227 lhs_range,
1228 expression_context,
1229 ),
1230 ),
1231 };
1232 (
1233 lhs,
1234 rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1235 )
1236 }
1237 false => {
1238 let lhs = if contains_comments(&*lhs) {
1241 hang_binop_expression(
1242 ctx,
1243 *lhs,
1244 binop.to_owned(),
1245 shape,
1246 lhs_range,
1247 expression_context,
1248 )
1249 } else {
1250 let context = if let BinOp::Caret(_) = binop {
1251 ExpressionContext::BinaryLHSExponent
1252 } else {
1253 ExpressionContext::BinaryLHS
1254 };
1255 format_expression_internal(ctx, &lhs, context, shape)
1256 };
1257
1258 let rhs = if contains_comments(&*rhs) {
1259 hang_binop_expression(
1260 ctx,
1261 *rhs,
1262 binop,
1263 shape,
1264 lhs_range,
1265 expression_context,
1266 )
1267 } else {
1268 format_expression_internal(
1269 ctx,
1270 &rhs,
1271 ExpressionContext::UnaryOrBinary,
1272 shape,
1273 )
1274 };
1275
1276 (lhs, rhs)
1277 }
1278 };
1279
1280 Expression::BinaryOperator {
1281 lhs: Box::new(lhs),
1282 binop: new_binop,
1283 rhs: Box::new(rhs),
1284 }
1285 }
1286 _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1288 }
1289}
1290
1291fn format_hanging_expression_(
1293 ctx: &Context,
1294 expression: &Expression,
1295 shape: Shape,
1296 expression_context: ExpressionContext,
1297 lhs_range: Option<LeftmostRangeHang>,
1298) -> Expression {
1299 let expression_range = expression.to_range();
1300
1301 match expression {
1302 #[cfg(feature = "luau")]
1303 Expression::TypeAssertion {
1304 expression,
1305 type_assertion,
1306 } => {
1307 let (expression_context, value_shape) = (
1310 ExpressionContext::TypeAssertion,
1311 shape.take_first_line(&strip_trivia(type_assertion)),
1312 );
1313
1314 let expression = format_hanging_expression_(
1315 ctx,
1316 expression,
1317 value_shape,
1318 expression_context,
1319 lhs_range,
1320 );
1321
1322 #[cfg(feature = "luau")]
1324 let assertion_shape = shape.take_last_line(&expression);
1325
1326 Expression::TypeAssertion {
1327 expression: Box::new(expression),
1328 type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1329 }
1330 }
1331 Expression::Parentheses {
1332 contained,
1333 expression,
1334 } => {
1335 let lhs_shape = if let Some(lhs_hang) = lhs_range {
1336 lhs_hang.required_shape(shape, &expression_range)
1337 } else {
1338 shape
1339 };
1340 #[cfg(feature = "luau")]
1341 let keep_parentheses = matches!(
1342 expression_context,
1343 ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1344 );
1345 #[cfg(not(feature = "luau"))]
1346 let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1347
1348 let use_internal_expression = check_excess_parentheses(expression, expression_context);
1351
1352 if use_internal_expression && !keep_parentheses {
1354 format_hanging_expression_(
1355 ctx,
1356 expression,
1357 lhs_shape,
1358 expression_context,
1359 lhs_range,
1360 )
1361 } else {
1362 let contained = format_contained_span(ctx, contained, lhs_shape);
1363
1364 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); let expression_str = formatted_expression.to_string();
1369 if !contains_comments(expression)
1370 && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1371 {
1372 return Expression::Parentheses {
1374 contained,
1375 expression: Box::new(formatted_expression),
1376 };
1377 }
1378
1379 let expression_shape = lhs_shape.reset().increment_additional_indent();
1381
1382 let (start_token, end_token) = contained.tokens();
1384
1385 let contained = ContainedSpan::new(
1388 start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1389 create_newline_trivia(ctx),
1390 create_indent_trivia(ctx, expression_shape),
1391 ])),
1392 end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1393 create_newline_trivia(ctx),
1394 create_indent_trivia(ctx, lhs_shape),
1395 ])),
1396 );
1397
1398 Expression::Parentheses {
1399 contained,
1400 expression: Box::new(format_hanging_expression_(
1401 ctx,
1402 expression,
1403 expression_shape,
1404 ExpressionContext::Standard,
1405 None,
1406 )),
1407 }
1408 }
1409 }
1410 Expression::UnaryOperator { unop, expression } => {
1411 let unop = format_unop(ctx, unop, shape);
1412 let shape = shape + strip_leading_trivia(&unop).to_string().len();
1413 let expression = format_hanging_expression_(
1414 ctx,
1415 expression,
1416 shape,
1417 ExpressionContext::UnaryOrBinary,
1418 lhs_range,
1419 );
1420
1421 Expression::UnaryOperator {
1422 unop,
1423 expression: Box::new(expression),
1424 }
1425 }
1426 Expression::BinaryOperator { lhs, binop, rhs } => {
1427 let lhs = hang_binop_expression(
1429 ctx,
1430 *lhs.to_owned(),
1431 binop.to_owned(),
1432 shape,
1433 lhs_range,
1434 ExpressionContext::UnaryOrBinary,
1435 );
1436
1437 let current_shape = shape.take_last_line(&lhs) + 1; let mut new_binop = format_binop(ctx, binop, current_shape);
1439
1440 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; let mut new_rhs = hang_binop_expression(
1443 ctx,
1444 *rhs.to_owned(),
1445 binop.to_owned(),
1446 singleline_shape,
1447 None,
1448 ExpressionContext::Standard,
1449 );
1450
1451 if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1453 || (did_hang_expression(&new_rhs)
1454 && binop_precedence_level(&new_rhs) >= binop.precedence())
1455 || contains_comments(binop)
1456 || lhs.has_trailing_comments(CommentSearch::All)
1457 || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1458 {
1459 let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1460 new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1461 new_rhs = hang_binop_expression(
1462 ctx,
1463 *rhs.to_owned(),
1464 binop.to_owned(),
1465 hanging_shape,
1466 None,
1467 ExpressionContext::Standard,
1468 )
1469 .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1470 }
1471
1472 Expression::BinaryOperator {
1473 lhs: Box::new(lhs),
1474 binop: new_binop,
1475 rhs: Box::new(new_rhs),
1476 }
1477 }
1478 _ => {
1479 let value_shape = if let Some(lhs_hang) = lhs_range {
1480 lhs_hang.required_shape(shape, &expression_range)
1481 } else {
1482 shape
1483 };
1484
1485 format_expression_internal(ctx, expression, expression_context, value_shape)
1486 }
1487 }
1488}
1489
1490pub fn hang_expression(
1491 ctx: &Context,
1492 expression: &Expression,
1493 shape: Shape,
1494 hang_level: Option<usize>,
1495) -> Expression {
1496 let original_additional_indent_level = shape.indent().additional_indent();
1497 let shape = match hang_level {
1498 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1499 None => shape,
1500 };
1501
1502 let lhs_range =
1503 hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1504
1505 format_hanging_expression_(
1506 ctx,
1507 expression,
1508 shape,
1509 ExpressionContext::Standard,
1510 lhs_range,
1511 )
1512}
1513
1514pub fn hang_expression_trailing_newline(
1515 ctx: &Context,
1516 expression: &Expression,
1517 shape: Shape,
1518 hang_level: Option<usize>,
1519) -> Expression {
1520 hang_expression(ctx, expression, shape, hang_level)
1521 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1522}