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 .map_or(false, |else_ifs| {
670 else_ifs.iter().any(trivia_util::contains_comments)
671 })
672 || if_expression.else_expression().has_inline_comments()
673 || trivia_util::spans_multiple_lines(&singleline_condition)
674 || trivia_util::spans_multiple_lines(&singleline_expression)
675 || else_ifs.as_ref().map_or(false, |else_ifs| {
676 else_ifs.iter().any(trivia_util::spans_multiple_lines)
677 })
678 || trivia_util::spans_multiple_lines(&singleline_else_expression);
679
680 if require_multiline_expression {
681 let condition = hang_expression_trailing_newline(
682 ctx,
683 if_expression.condition(),
684 shape.increment_additional_indent(),
685 Some(1),
686 );
687 let hanging_shape = shape.reset().increment_additional_indent();
688
689 let (then_token, expression) = format_token_expression_sequence(
691 ctx,
692 if_expression.then_token(),
693 if_expression.if_expression(),
694 hanging_shape,
695 );
696
697 let then_token =
699 then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
700 ctx,
701 hanging_shape,
702 )]));
703
704 let else_ifs = if_expression
706 .else_if_expressions()
707 .map(|else_if_expressions| {
708 else_if_expressions
709 .iter()
710 .map(|else_if_expression| {
711 let singleline_else_if = format_else_if_expression_singleline(
712 ctx,
713 else_if_expression,
714 hanging_shape,
715 );
716 let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
717
718 if singleline_shape.over_budget()
719 || else_if_expression
720 .else_if_token()
721 .has_trailing_comments(CommentSearch::All)
722 || trivia_util::contains_comments(else_if_expression.condition())
723 || trivia_util::contains_comments(else_if_expression.then_token())
724 {
725 let else_if_token = fmt_symbol!(
726 ctx,
727 else_if_expression.else_if_token(),
728 "elseif",
729 shape
730 )
731 .update_leading_trivia(FormatTriviaType::Append(vec![
732 create_newline_trivia(ctx),
733 create_indent_trivia(ctx, hanging_shape),
734 ]));
735
736 let condiiton_shape =
737 hanging_shape.reset().increment_additional_indent();
738 let else_if_condition = hang_expression(
739 ctx,
740 &remove_condition_parentheses(
741 else_if_expression.condition().to_owned(),
742 ),
743 condiiton_shape,
744 None,
745 )
746 .update_leading_trivia(FormatTriviaType::Append(vec![
747 create_newline_trivia(ctx),
748 create_indent_trivia(ctx, condiiton_shape),
749 ]));
750
751 let hanging_shape =
752 hanging_shape.take_first_line(&else_if_condition) + 13; let (then_token, expression) = format_token_expression_sequence(
755 ctx,
756 else_if_expression.then_token(),
757 else_if_expression.expression(),
758 hanging_shape,
759 );
760
761 let then_token =
762 then_token.update_leading_trivia(FormatTriviaType::Append(vec![
763 create_newline_trivia(ctx),
764 create_indent_trivia(ctx, hanging_shape),
765 ]));
766
767 ElseIfExpression::new(else_if_condition, expression)
768 .with_else_if_token(else_if_token)
769 .with_then_token(then_token)
770 } else {
771 singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
772 vec![
773 create_newline_trivia(ctx),
774 create_indent_trivia(ctx, hanging_shape),
775 ],
776 ))
777 }
778 })
779 .collect::<Vec<_>>()
780 });
781
782 let (else_token, else_expression) = format_token_expression_sequence(
784 ctx,
785 if_expression.else_token(),
786 if_expression.else_expression(),
787 hanging_shape + 5, );
789
790 let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
792
793 IfExpression::new(condition, expression, else_expression)
794 .with_if_token(if_token)
795 .with_then_token(then_token)
796 .with_else_if(else_ifs)
797 .with_else_token(else_token)
798 } else {
799 let else_ifs = else_ifs.map(|x| {
801 x.iter()
802 .map(|x| {
803 x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
804 TokenType::spaces(1),
805 )]))
806 })
807 .collect()
808 });
809
810 IfExpression::new(
811 singleline_condition,
812 singleline_expression,
813 singleline_else_expression,
814 )
815 .with_if_token(if_token)
816 .with_then_token(then_token)
817 .with_else_if(else_ifs)
818 .with_else_token(else_token)
819 }
820}
821
822#[cfg(feature = "luau")]
823fn format_interpolated_string(
824 ctx: &Context,
825 interpolated_string: &InterpolatedString,
826 shape: Shape,
827) -> InterpolatedString {
828 let mut shape = shape;
829
830 let mut segments = Vec::new();
831 for segment in interpolated_string.segments() {
832 let literal = format_token_reference(ctx, &segment.literal, shape);
833 shape = shape + literal.to_string().len();
834
835 let mut expression = format_expression(ctx, &segment.expression, shape);
836 shape = shape.take_last_line(&expression);
837
838 if let Expression::TableConstructor { .. } = expression {
841 expression =
842 expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
843 TokenType::spaces(1),
844 )]))
845 }
846
847 segments.push(InterpolatedStringSegment {
848 literal,
849 expression,
850 })
851 }
852
853 interpolated_string
854 .to_owned()
855 .with_segments(segments)
856 .with_last_string(format_token_reference(
857 ctx,
858 interpolated_string.last_string(),
859 shape,
860 ))
861}
862
863pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
865 match var {
866 Var::Name(token_reference) => {
867 Var::Name(format_token_reference(ctx, token_reference, shape))
868 }
869 Var::Expression(var_expression) => {
870 Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
871 }
872 other => panic!("unknown node {:?}", other),
873 }
874}
875
876pub fn format_var_expression(
877 ctx: &Context,
878 var_expression: &VarExpression,
879 shape: Shape,
880) -> VarExpression {
881 let function_call = format_function_call(
884 ctx,
885 &FunctionCall::new(var_expression.prefix().clone())
886 .with_suffixes(var_expression.suffixes().cloned().collect()),
887 shape,
888 );
889 VarExpression::new(function_call.prefix().clone())
890 .with_suffixes(function_call.suffixes().cloned().collect())
891}
892
893pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
895 fmt_op!(ctx, UnOp, unop, shape, {
896 Minus = "-",
897 Not = "not ",
898 Hash = "#",
899 #[cfg(feature = "lua53")]
900 Tilde = "~",
901 }, |other| panic!("unknown node {:?}", other))
902}
903
904fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
909 let mut leading_comments = binop
913 .leading_comments()
914 .iter()
915 .flat_map(|x| {
916 vec![
917 create_newline_trivia(ctx),
918 create_indent_trivia(ctx, shape),
919 x.to_owned(),
920 ]
921 })
922 .collect::<Vec<_>>();
923
924 let mut trailing_comments = binop.trailing_comments();
926 leading_comments.append(&mut trailing_comments);
927
928 let mut expression_leading_comments = rhs
930 .leading_comments()
931 .iter()
932 .flat_map(|x| {
933 vec![
934 create_newline_trivia(ctx),
935 create_indent_trivia(ctx, shape),
936 x.to_owned(),
937 ]
938 })
939 .collect::<Vec<_>>();
940 leading_comments.append(&mut expression_leading_comments);
941
942 leading_comments.push(create_newline_trivia(ctx));
944 leading_comments.push(create_indent_trivia(ctx, shape));
945
946 binop.update_trivia(
947 FormatTriviaType::Replace(leading_comments),
948 FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
949 )
950}
951
952fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
954 match expression {
955 Expression::BinaryOperator { lhs, binop, rhs } => {
956 if binop.precedence() >= top_binop.precedence()
957 && binop.is_right_associative() == top_binop.is_right_associative()
958 {
959 if binop.is_right_associative() {
960 binop_expression_length(rhs, top_binop)
961 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**lhs).to_string().len()
963 } else {
964 binop_expression_length(lhs, top_binop)
965 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**rhs).to_string().len()
967 }
968 } else {
969 0
970 }
971 }
972 _ => strip_trivia(expression).to_string().len(),
973 }
974}
975
976fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
977 match expression {
978 Expression::BinaryOperator { lhs, binop, rhs } => {
979 if binop.precedence() == top_binop.precedence() {
980 contains_comments(binop)
981 || rhs.has_leading_comments(CommentSearch::All)
982 || lhs.has_trailing_comments(CommentSearch::All)
983 || binop_expression_contains_comments(lhs, top_binop)
984 || binop_expression_contains_comments(rhs, top_binop)
985 } else {
986 false
987 }
988 }
989 _ => false,
990 }
991}
992
993trait ToRange {
995 fn to_range(&self) -> (usize, usize);
996}
997
998impl ToRange for (usize, usize) {
999 fn to_range(&self) -> (usize, usize) {
1000 *self
1001 }
1002}
1003
1004impl ToRange for Expression {
1005 fn to_range(&self) -> (usize, usize) {
1006 let (start, end) = self.range().unwrap();
1007 (start.bytes(), end.bytes())
1008 }
1009}
1010
1011#[derive(Clone, Copy, Debug)]
1037struct LeftmostRangeHang {
1038 range: (usize, usize),
1039 original_additional_indent_level: usize,
1040}
1041
1042impl LeftmostRangeHang {
1043 fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1046 match expression {
1047 Expression::BinaryOperator { lhs, .. } => {
1048 Self::find(lhs, original_additional_indent_level)
1049 }
1050 _ => Self {
1051 range: expression.to_range(),
1052 original_additional_indent_level,
1053 },
1054 }
1055 }
1056
1057 fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1061 let (expression_start, expression_end) = item.to_range();
1062 let (lhs_start, lhs_end) = self.range;
1063
1064 if lhs_start >= expression_start && lhs_end <= expression_end {
1065 shape.with_indent(
1066 shape
1067 .indent()
1068 .with_additional_indent(self.original_additional_indent_level),
1069 )
1070 } else {
1071 shape
1072 }
1073 }
1074}
1075
1076fn is_hang_binop_over_width(
1077 shape: Shape,
1078 expression: &Expression,
1079 top_binop: &BinOp,
1080 lhs_range: Option<LeftmostRangeHang>,
1081) -> bool {
1082 let shape = if let Some(lhs_hang) = lhs_range {
1083 lhs_hang.required_shape(shape, expression)
1084 } else {
1085 shape
1086 };
1087
1088 shape
1089 .add_width(binop_expression_length(expression, top_binop))
1090 .over_budget()
1091}
1092
1093fn binop_precedence_level(expression: &Expression) -> u8 {
1095 match expression {
1096 Expression::BinaryOperator { binop, .. } => binop.precedence(),
1097 _ => 0,
1098 }
1099}
1100
1101fn did_hang_expression(expression: &Expression) -> bool {
1102 if let Expression::BinaryOperator { binop, .. } = expression {
1103 binop
1106 .surrounding_trivia()
1107 .0
1108 .iter()
1109 .any(|x| trivia_is_newline(x))
1110 } else {
1111 false
1112 }
1113}
1114
1115#[derive(Debug)]
1116enum ExpressionSide {
1117 Left,
1118 Right,
1119}
1120
1121fn hang_binop_expression(
1122 ctx: &Context,
1123 expression: Expression,
1124 top_binop: BinOp,
1125 shape: Shape,
1126 lhs_range: Option<LeftmostRangeHang>,
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 ),
1186 if contains_comments(&*rhs) {
1187 hang_binop_expression(ctx, *rhs, binop, shape, lhs_range)
1188 } else {
1189 format_expression_internal(
1190 ctx,
1191 &rhs,
1192 ExpressionContext::UnaryOrBinary,
1193 rhs_shape,
1194 )
1195 },
1196 ),
1197 ExpressionSide::Right => (
1198 if contains_comments(&*lhs) {
1199 hang_binop_expression(ctx, *lhs, binop.clone(), shape, lhs_range)
1200 } else {
1201 let context = if let BinOp::Caret(_) = binop {
1202 ExpressionContext::BinaryLHSExponent
1203 } else {
1204 ExpressionContext::BinaryLHS
1205 };
1206 format_expression_internal(ctx, &lhs, context, lhs_shape)
1207 },
1208 hang_binop_expression(
1209 ctx,
1210 *rhs,
1211 if same_op_level { top_binop } else { binop },
1212 rhs_shape,
1213 lhs_range,
1214 ),
1215 ),
1216 };
1217 (
1218 lhs,
1219 rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1220 )
1221 }
1222 false => {
1223 let lhs = if contains_comments(&*lhs) {
1226 hang_binop_expression(ctx, *lhs, binop.to_owned(), shape, lhs_range)
1227 } else {
1228 let context = if let BinOp::Caret(_) = binop {
1229 ExpressionContext::BinaryLHSExponent
1230 } else {
1231 ExpressionContext::BinaryLHS
1232 };
1233 format_expression_internal(ctx, &lhs, context, shape)
1234 };
1235
1236 let rhs = if contains_comments(&*rhs) {
1237 hang_binop_expression(ctx, *rhs, binop, shape, lhs_range)
1238 } else {
1239 format_expression_internal(
1240 ctx,
1241 &rhs,
1242 ExpressionContext::UnaryOrBinary,
1243 shape,
1244 )
1245 };
1246
1247 (lhs, rhs)
1248 }
1249 };
1250
1251 Expression::BinaryOperator {
1252 lhs: Box::new(lhs),
1253 binop: new_binop,
1254 rhs: Box::new(rhs),
1255 }
1256 }
1257 _ => format_hanging_expression_(
1259 ctx,
1260 &expression,
1261 shape,
1262 ExpressionContext::Standard,
1263 lhs_range,
1264 ),
1265 }
1266}
1267
1268fn format_hanging_expression_(
1270 ctx: &Context,
1271 expression: &Expression,
1272 shape: Shape,
1273 expression_context: ExpressionContext,
1274 lhs_range: Option<LeftmostRangeHang>,
1275) -> Expression {
1276 let expression_range = expression.to_range();
1277
1278 match expression {
1279 #[cfg(feature = "luau")]
1280 Expression::TypeAssertion {
1281 expression,
1282 type_assertion,
1283 } => {
1284 let (expression_context, value_shape) = (
1287 ExpressionContext::TypeAssertion,
1288 shape.take_first_line(&strip_trivia(type_assertion)),
1289 );
1290
1291 let expression = format_hanging_expression_(
1292 ctx,
1293 expression,
1294 value_shape,
1295 expression_context,
1296 lhs_range,
1297 );
1298
1299 #[cfg(feature = "luau")]
1301 let assertion_shape = shape.take_last_line(&expression);
1302
1303 Expression::TypeAssertion {
1304 expression: Box::new(expression),
1305 type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1306 }
1307 }
1308 Expression::Parentheses {
1309 contained,
1310 expression,
1311 } => {
1312 let lhs_shape = if let Some(lhs_hang) = lhs_range {
1313 lhs_hang.required_shape(shape, &expression_range)
1314 } else {
1315 shape
1316 };
1317 #[cfg(feature = "luau")]
1318 let keep_parentheses = matches!(
1319 expression_context,
1320 ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1321 );
1322 #[cfg(not(feature = "luau"))]
1323 let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1324
1325 let use_internal_expression = check_excess_parentheses(expression, expression_context);
1328
1329 if use_internal_expression && !keep_parentheses {
1331 format_hanging_expression_(
1332 ctx,
1333 expression,
1334 lhs_shape,
1335 expression_context,
1336 lhs_range,
1337 )
1338 } else {
1339 let contained = format_contained_span(ctx, contained, lhs_shape);
1340
1341 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); let expression_str = formatted_expression.to_string();
1346 if !contains_comments(expression)
1347 && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1348 {
1349 return Expression::Parentheses {
1351 contained,
1352 expression: Box::new(formatted_expression),
1353 };
1354 }
1355
1356 let expression_shape = lhs_shape.reset().increment_additional_indent();
1358
1359 let (start_token, end_token) = contained.tokens();
1361
1362 let contained = ContainedSpan::new(
1365 start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1366 create_newline_trivia(ctx),
1367 create_indent_trivia(ctx, expression_shape),
1368 ])),
1369 end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1370 create_newline_trivia(ctx),
1371 create_indent_trivia(ctx, lhs_shape),
1372 ])),
1373 );
1374
1375 Expression::Parentheses {
1376 contained,
1377 expression: Box::new(format_hanging_expression_(
1378 ctx,
1379 expression,
1380 expression_shape,
1381 ExpressionContext::Standard,
1382 None,
1383 )),
1384 }
1385 }
1386 }
1387 Expression::UnaryOperator { unop, expression } => {
1388 let unop = format_unop(ctx, unop, shape);
1389 let shape = shape + strip_leading_trivia(&unop).to_string().len();
1390 let expression = format_hanging_expression_(
1391 ctx,
1392 expression,
1393 shape,
1394 ExpressionContext::UnaryOrBinary,
1395 lhs_range,
1396 );
1397
1398 Expression::UnaryOperator {
1399 unop,
1400 expression: Box::new(expression),
1401 }
1402 }
1403 Expression::BinaryOperator { lhs, binop, rhs } => {
1404 let lhs =
1406 hang_binop_expression(ctx, *lhs.to_owned(), binop.to_owned(), shape, lhs_range);
1407
1408 let current_shape = shape.take_last_line(&lhs) + 1; let mut new_binop = format_binop(ctx, binop, current_shape);
1410
1411 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; let mut new_rhs = hang_binop_expression(
1414 ctx,
1415 *rhs.to_owned(),
1416 binop.to_owned(),
1417 singleline_shape,
1418 None,
1419 );
1420
1421 if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1423 || (did_hang_expression(&new_rhs)
1424 && binop_precedence_level(&new_rhs) >= binop.precedence())
1425 || contains_comments(binop)
1426 || lhs.has_trailing_comments(CommentSearch::All)
1427 || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1428 {
1429 let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1430 new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1431 new_rhs = hang_binop_expression(
1432 ctx,
1433 *rhs.to_owned(),
1434 binop.to_owned(),
1435 hanging_shape,
1436 None,
1437 )
1438 .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1439 }
1440
1441 Expression::BinaryOperator {
1442 lhs: Box::new(lhs),
1443 binop: new_binop,
1444 rhs: Box::new(new_rhs),
1445 }
1446 }
1447 _ => {
1448 let value_shape = if let Some(lhs_hang) = lhs_range {
1449 lhs_hang.required_shape(shape, &expression_range)
1450 } else {
1451 shape
1452 };
1453
1454 format_expression_internal(ctx, expression, expression_context, value_shape)
1455 }
1456 }
1457}
1458
1459pub fn hang_expression(
1460 ctx: &Context,
1461 expression: &Expression,
1462 shape: Shape,
1463 hang_level: Option<usize>,
1464) -> Expression {
1465 let original_additional_indent_level = shape.indent().additional_indent();
1466 let shape = match hang_level {
1467 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1468 None => shape,
1469 };
1470
1471 let lhs_range =
1472 hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1473
1474 format_hanging_expression_(
1475 ctx,
1476 expression,
1477 shape,
1478 ExpressionContext::Standard,
1479 lhs_range,
1480 )
1481}
1482
1483pub fn hang_expression_trailing_newline(
1484 ctx: &Context,
1485 expression: &Expression,
1486 shape: Shape,
1487 hang_level: Option<usize>,
1488) -> Expression {
1489 hang_expression(ctx, expression, shape, hang_level)
1490 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1491}