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 (leading_comments, trailing_comments) =
257 contained_span_comments(ctx, contained, shape);
258
259 format_expression(ctx, expression, shape)
260 .update_leading_trivia(FormatTriviaType::Append(leading_comments))
261 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments))
262 } else {
263 Expression::Parentheses {
264 contained: format_contained_span(ctx, contained, shape),
265 expression: Box::new(format_expression(ctx, expression, shape + 1)), }
267 }
268 }
269 Expression::UnaryOperator { unop, expression } => {
270 let unop = format_unop(ctx, unop, shape);
271 let shape = shape + strip_leading_trivia(&unop).to_string().len();
272 let mut expression = format_expression_internal(
273 ctx,
274 expression,
275 ExpressionContext::UnaryOrBinary,
276 shape,
277 );
278
279 if let UnOp::Minus(_) = unop {
282 let require_parentheses = match expression {
283 Expression::UnaryOperator {
284 unop: UnOp::Minus(_),
285 ..
286 } => true,
287 Expression::Parentheses { ref expression, .. } => matches!(
288 &**expression,
289 Expression::UnaryOperator {
290 unop: UnOp::Minus(_),
291 ..
292 }
293 ),
294 _ => false,
295 };
296
297 if require_parentheses {
298 let (new_expression, trailing_comments) =
299 trivia_util::take_trailing_comments(&expression);
300 expression = Expression::Parentheses {
301 contained: ContainedSpan::new(
302 TokenReference::symbol("(").unwrap(),
303 TokenReference::symbol(")").unwrap(),
304 )
305 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments)),
306 expression: Box::new(new_expression),
307 }
308 }
309 }
310
311 Expression::UnaryOperator {
312 unop,
313 expression: Box::new(expression),
314 }
315 }
316 Expression::BinaryOperator { lhs, binop, rhs } => {
317 let context = if let BinOp::Caret(_) = binop {
318 ExpressionContext::BinaryLHSExponent
319 } else {
320 ExpressionContext::BinaryLHS
321 };
322 let lhs = format_expression_internal(ctx, lhs, context, shape);
323 let binop = format_binop(ctx, binop, shape);
324 let shape = shape.take_last_line(&lhs) + binop.to_string().len();
325 Expression::BinaryOperator {
326 lhs: Box::new(lhs),
327 binop,
328 rhs: Box::new(format_expression_internal(
329 ctx,
330 rhs,
331 ExpressionContext::UnaryOrBinary,
332 shape,
333 )),
334 }
335 }
336 other => panic!("unknown node {:?}", other),
337 }
338}
339
340fn contained_span_comments(
341 ctx: &Context,
342 contained_span: &ContainedSpan,
343 shape: Shape,
344) -> (Vec<Token>, Vec<Token>) {
345 let (start_parens, end_parens) = contained_span.tokens();
347 let leading_comments = start_parens
348 .leading_trivia()
349 .filter(|token| trivia_util::trivia_is_comment(token))
350 .flat_map(|x| {
351 vec![
352 create_indent_trivia(ctx, shape),
353 x.to_owned(),
354 create_newline_trivia(ctx),
355 ]
356 })
357 .collect();
359
360 let trailing_comments = end_parens
361 .trailing_trivia()
362 .filter(|token| trivia_util::trivia_is_comment(token))
363 .flat_map(|x| {
364 vec![Token::new(TokenType::spaces(1)), x.to_owned()]
366 })
367 .collect();
368 (leading_comments, trailing_comments)
369}
370
371pub fn is_brackets_string(expression: &Expression) -> bool {
374 match expression {
375 Expression::String(token_reference) => matches!(
376 token_reference.token_type(),
377 TokenType::StringLiteral {
378 quote_type: StringLiteralQuoteType::Brackets,
379 ..
380 }
381 ),
382 Expression::Parentheses { expression, .. } => is_brackets_string(expression),
383 #[cfg(feature = "luau")]
384 Expression::TypeAssertion { expression, .. } => is_brackets_string(expression),
385 _ => false,
386 }
387}
388
389pub fn process_dot_name(
390 ctx: &Context,
391 dot: &TokenReference,
392 name: &TokenReference,
393 shape: Shape,
394) -> (TokenReference, TokenReference) {
395 let (mut dot, mut dot_comments) =
398 take_trailing_comments(&format_token_reference(ctx, dot, shape));
399 let (name, name_comments) = take_leading_comments(&format_token_reference(ctx, name, shape));
400
401 dot_comments.extend(name_comments);
402
403 if !dot_comments.is_empty() {
404 dot = prepend_newline_indent(
405 ctx,
406 &dot.update_leading_trivia(FormatTriviaType::Append(dot_comments)),
407 shape,
408 );
409 }
410
411 (dot, name)
412}
413
414pub fn format_index(ctx: &Context, index: &Index, shape: Shape) -> Index {
416 match index {
417 Index::Brackets {
418 brackets,
419 expression,
420 } => {
421 if brackets
422 .tokens()
423 .0
424 .has_trailing_comments(CommentSearch::All)
425 || contains_comments(expression)
426 || brackets.tokens().1.has_leading_comments(CommentSearch::All)
427 {
428 let (start_bracket, end_bracket) = brackets.tokens();
429
430 let indent_shape = shape.reset().increment_additional_indent();
431
432 let brackets = ContainedSpan::new(
434 fmt_symbol!(ctx, start_bracket, "[", shape).update_trailing_trivia(
435 FormatTriviaType::Append(vec![
436 create_newline_trivia(ctx),
437 create_indent_trivia(ctx, indent_shape),
438 ]),
439 ),
440 format_end_token(ctx, end_bracket, EndTokenType::IndentComments, shape)
441 .update_leading_trivia(FormatTriviaType::Append(vec![
442 create_indent_trivia(ctx, shape),
443 ])),
444 );
445
446 let expression = format_expression(ctx, expression, indent_shape)
447 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(
448 ctx,
449 )]));
450
451 Index::Brackets {
452 brackets,
453 expression,
454 }
455 } else if is_brackets_string(expression) {
456 Index::Brackets {
457 brackets: format_contained_span(ctx, brackets, shape),
458 expression: format_expression(ctx, expression, shape + 2) .update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
460 TokenType::spaces(1),
461 )]))
462 .update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
463 TokenType::spaces(1),
464 )])),
465 }
466 } else {
467 Index::Brackets {
468 brackets: format_contained_span(ctx, brackets, shape),
469 expression: format_expression(ctx, expression, shape + 1), }
471 }
472 }
473
474 Index::Dot { dot, name } => {
475 let (dot, name) = process_dot_name(ctx, dot, name, shape);
476 Index::Dot { dot, name }
477 }
478 other => panic!("unknown node {:?}", other),
479 }
480}
481
482fn is_string(expression: &Expression) -> bool {
484 match expression {
485 Expression::String(_) => true,
486 #[cfg(feature = "luau")]
487 Expression::InterpolatedString(_) => true,
488 Expression::Parentheses { expression, .. } => is_string(expression),
489 _ => false,
490 }
491}
492
493pub fn format_prefix(ctx: &Context, prefix: &Prefix, shape: Shape) -> Prefix {
495 match prefix {
496 Prefix::Expression(expression) => {
497 let singleline_format =
498 format_expression_internal(ctx, expression, ExpressionContext::Prefix, shape);
499 let singeline_shape = shape.take_first_line(&strip_trivia(&singleline_format));
500
501 if singeline_shape.over_budget() && !is_string(expression) {
502 Prefix::Expression(Box::new(format_hanging_expression_(
503 ctx,
504 expression,
505 shape,
506 ExpressionContext::Prefix,
507 None,
508 )))
509 } else {
510 Prefix::Expression(Box::new(singleline_format))
511 }
512 }
513 Prefix::Name(token_reference) => {
514 Prefix::Name(format_token_reference(ctx, token_reference, shape))
515 }
516 other => panic!("unknown node {:?}", other),
517 }
518}
519
520pub fn format_suffix(
522 ctx: &Context,
523 suffix: &Suffix,
524 shape: Shape,
525 call_next_node: FunctionCallNextNode,
526) -> Suffix {
527 match suffix {
528 Suffix::Call(call) => Suffix::Call(format_call(ctx, call, shape, call_next_node)),
529 Suffix::Index(index) => Suffix::Index(format_index(ctx, index, shape)),
530 other => panic!("unknown node {:?}", other),
531 }
532}
533
534#[cfg(feature = "luau")]
537fn format_else_if_expression_singleline(
538 ctx: &Context,
539 else_if_expression: &ElseIfExpression,
540 shape: Shape,
541) -> ElseIfExpression {
542 let else_if_token = fmt_symbol!(ctx, else_if_expression.else_if_token(), "elseif ", shape);
543 let else_if_condition = remove_condition_parentheses(else_if_expression.condition().to_owned());
544 let else_if_condition = format_expression(ctx, &else_if_condition, shape + 7); let (then_token, expression) = format_token_expression_sequence(
546 ctx,
547 else_if_expression.then_token(),
548 else_if_expression.expression(),
549 shape.take_first_line(&else_if_condition) + 13, );
551
552 let then_token = then_token.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
554 TokenType::spaces(1),
555 )]));
556
557 ElseIfExpression::new(else_if_condition, expression)
558 .with_else_if_token(else_if_token)
559 .with_then_token(then_token)
560}
561
562#[cfg(feature = "luau")]
565fn format_token_expression_sequence(
566 ctx: &Context,
567 token: &TokenReference,
568 expression: &Expression,
569 shape: Shape,
570) -> (TokenReference, Expression) {
571 const SPACE_LEN: usize = " ".len();
572 let formatted_token = format_token_reference(ctx, token, shape);
573 let token_width = strip_trivia(&formatted_token).to_string().len();
574
575 let formatted_expression =
576 format_expression(ctx, expression, shape.add_width(token_width + SPACE_LEN));
577
578 let requires_multiline_expression = shape.take_first_line(&formatted_expression).over_budget()
579 || token.has_trailing_comments(CommentSearch::All)
580 || trivia_util::contains_comments(
581 expression.update_trailing_trivia(FormatTriviaType::Replace(vec![])),
582 ); let newline_after_token = token.has_trailing_comments(CommentSearch::Single)
585 || expression.has_leading_comments(CommentSearch::Single);
586
587 let token = match newline_after_token {
588 true => formatted_token
590 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)])),
591 false => {
593 formatted_token.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
594 TokenType::spaces(1),
595 )]))
596 }
597 };
598
599 let expression = match requires_multiline_expression {
600 true => match newline_after_token {
601 true => {
602 let shape = shape.reset().increment_additional_indent();
603 hang_expression(ctx, expression, shape, calculate_hang_level(expression))
604 .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
605 ctx, shape,
606 )]))
607 }
608 false => hang_expression(
609 ctx,
610 expression,
611 shape.add_width(token_width + SPACE_LEN),
612 calculate_hang_level(expression),
613 ),
614 },
615 false => formatted_expression,
616 };
617
618 (token, expression)
619}
620
621#[cfg(feature = "luau")]
623fn format_if_expression(ctx: &Context, if_expression: &IfExpression, shape: Shape) -> IfExpression {
624 let condition = remove_condition_parentheses(if_expression.condition().to_owned());
626 let if_token = fmt_symbol!(ctx, if_expression.if_token(), "if ", shape);
627
628 let singleline_condition = format_expression(ctx, &condition, shape.with_infinite_width());
630 let then_token = fmt_symbol!(ctx, if_expression.then_token(), " then ", shape);
631 let singleline_expression = format_expression(
632 ctx,
633 if_expression.if_expression(),
634 shape.with_infinite_width(),
635 );
636 let else_ifs = if_expression
637 .else_if_expressions()
638 .map(|else_if_expressions| {
639 else_if_expressions
640 .iter()
641 .map(|else_if_expression| {
642 format_else_if_expression_singleline(
643 ctx,
644 else_if_expression,
645 shape.with_infinite_width(),
646 )
647 })
648 .collect::<Vec<_>>()
649 });
650 let else_token = fmt_symbol!(ctx, if_expression.else_token(), " else ", shape);
651 let singleline_else_expression = format_expression(
652 ctx,
653 if_expression.else_expression(),
654 shape.with_infinite_width(),
655 );
656
657 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)
663 .take_first_line(&strip_trivia(&singleline_condition))
664 .take_first_line(&strip_trivia(&singleline_expression))
665 .take_first_line(&else_ifs.as_ref().map_or(String::new(), |x| {
666 x.iter().map(|x| x.to_string()).collect::<String>()
667 }))
668 .take_first_line(&strip_trivia(&singleline_else_expression));
669
670 let require_multiline_expression = singleline_shape.over_budget()
671 || if_expression
672 .if_token()
673 .has_trailing_comments(CommentSearch::All)
674 || trivia_util::contains_comments(if_expression.condition())
675 || trivia_util::contains_comments(if_expression.then_token())
676 || trivia_util::contains_comments(if_expression.if_expression())
677 || trivia_util::contains_comments(if_expression.else_token())
678 || if_expression
679 .else_if_expressions()
680 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::contains_comments))
681 || if_expression.else_expression().has_inline_comments()
682 || trivia_util::spans_multiple_lines(&singleline_condition)
683 || trivia_util::spans_multiple_lines(&singleline_expression)
684 || else_ifs
685 .as_ref()
686 .is_some_and(|else_ifs| else_ifs.iter().any(trivia_util::spans_multiple_lines))
687 || trivia_util::spans_multiple_lines(&singleline_else_expression);
688
689 if require_multiline_expression {
690 let condition = hang_expression_trailing_newline(
691 ctx,
692 if_expression.condition(),
693 shape.increment_additional_indent(),
694 Some(1),
695 );
696 let hanging_shape = shape.reset().increment_additional_indent();
697
698 let (then_token, expression) = format_token_expression_sequence(
700 ctx,
701 if_expression.then_token(),
702 if_expression.if_expression(),
703 hanging_shape,
704 );
705
706 let then_token =
708 then_token.update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
709 ctx,
710 hanging_shape,
711 )]));
712
713 let else_ifs = if_expression
715 .else_if_expressions()
716 .map(|else_if_expressions| {
717 else_if_expressions
718 .iter()
719 .map(|else_if_expression| {
720 let singleline_else_if = format_else_if_expression_singleline(
721 ctx,
722 else_if_expression,
723 hanging_shape,
724 );
725 let singleline_shape = hanging_shape.take_first_line(&singleline_else_if);
726
727 if singleline_shape.over_budget()
728 || else_if_expression
729 .else_if_token()
730 .has_trailing_comments(CommentSearch::All)
731 || trivia_util::contains_comments(else_if_expression.condition())
732 || trivia_util::contains_comments(else_if_expression.then_token())
733 {
734 let else_if_token = fmt_symbol!(
735 ctx,
736 else_if_expression.else_if_token(),
737 "elseif",
738 shape
739 )
740 .update_leading_trivia(FormatTriviaType::Append(vec![
741 create_newline_trivia(ctx),
742 create_indent_trivia(ctx, hanging_shape),
743 ]));
744
745 let condiiton_shape =
746 hanging_shape.reset().increment_additional_indent();
747 let else_if_condition = hang_expression(
748 ctx,
749 &remove_condition_parentheses(
750 else_if_expression.condition().to_owned(),
751 ),
752 condiiton_shape,
753 None,
754 )
755 .update_leading_trivia(FormatTriviaType::Append(vec![
756 create_newline_trivia(ctx),
757 create_indent_trivia(ctx, condiiton_shape),
758 ]));
759
760 let hanging_shape =
761 hanging_shape.take_first_line(&else_if_condition) + 13; let (then_token, expression) = format_token_expression_sequence(
764 ctx,
765 else_if_expression.then_token(),
766 else_if_expression.expression(),
767 hanging_shape,
768 );
769
770 let then_token =
771 then_token.update_leading_trivia(FormatTriviaType::Append(vec![
772 create_newline_trivia(ctx),
773 create_indent_trivia(ctx, hanging_shape),
774 ]));
775
776 ElseIfExpression::new(else_if_condition, expression)
777 .with_else_if_token(else_if_token)
778 .with_then_token(then_token)
779 } else {
780 singleline_else_if.update_leading_trivia(FormatTriviaType::Append(
781 vec![
782 create_newline_trivia(ctx),
783 create_indent_trivia(ctx, hanging_shape),
784 ],
785 ))
786 }
787 })
788 .collect::<Vec<_>>()
789 });
790
791 let (else_token, else_expression) = format_token_expression_sequence(
793 ctx,
794 if_expression.else_token(),
795 if_expression.else_expression(),
796 hanging_shape + 5, );
798
799 let else_token = trivia_util::prepend_newline_indent(ctx, &else_token, hanging_shape);
801
802 IfExpression::new(condition, expression, else_expression)
803 .with_if_token(if_token)
804 .with_then_token(then_token)
805 .with_else_if(else_ifs)
806 .with_else_token(else_token)
807 } else {
808 let else_ifs = else_ifs.map(|x| {
810 x.iter()
811 .map(|x| {
812 x.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
813 TokenType::spaces(1),
814 )]))
815 })
816 .collect()
817 });
818
819 IfExpression::new(
820 singleline_condition,
821 singleline_expression,
822 singleline_else_expression,
823 )
824 .with_if_token(if_token)
825 .with_then_token(then_token)
826 .with_else_if(else_ifs)
827 .with_else_token(else_token)
828 }
829}
830
831#[cfg(feature = "luau")]
832fn format_interpolated_string(
833 ctx: &Context,
834 interpolated_string: &InterpolatedString,
835 shape: Shape,
836) -> InterpolatedString {
837 let mut shape = shape;
838
839 let mut segments = Vec::new();
840 for segment in interpolated_string.segments() {
841 let literal = format_token_reference(ctx, &segment.literal, shape);
842 shape = shape + literal.to_string().len();
843
844 let mut expression = format_expression(ctx, &segment.expression, shape);
845 shape = shape.take_last_line(&expression);
846
847 if let Expression::TableConstructor { .. } = expression {
850 expression =
851 expression.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
852 TokenType::spaces(1),
853 )]))
854 }
855
856 segments.push(InterpolatedStringSegment {
857 literal,
858 expression,
859 })
860 }
861
862 interpolated_string
863 .to_owned()
864 .with_segments(segments)
865 .with_last_string(format_token_reference(
866 ctx,
867 interpolated_string.last_string(),
868 shape,
869 ))
870}
871
872pub fn format_var(ctx: &Context, var: &Var, shape: Shape) -> Var {
874 match var {
875 Var::Name(token_reference) => {
876 Var::Name(format_token_reference(ctx, token_reference, shape))
877 }
878 Var::Expression(var_expression) => {
879 Var::Expression(Box::new(format_var_expression(ctx, var_expression, shape)))
880 }
881 other => panic!("unknown node {:?}", other),
882 }
883}
884
885pub fn format_var_expression(
886 ctx: &Context,
887 var_expression: &VarExpression,
888 shape: Shape,
889) -> VarExpression {
890 let function_call = format_function_call(
893 ctx,
894 &FunctionCall::new(var_expression.prefix().clone())
895 .with_suffixes(var_expression.suffixes().cloned().collect()),
896 shape,
897 );
898 VarExpression::new(function_call.prefix().clone())
899 .with_suffixes(function_call.suffixes().cloned().collect())
900}
901
902pub fn format_unop(ctx: &Context, unop: &UnOp, shape: Shape) -> UnOp {
904 fmt_op!(ctx, UnOp, unop, shape, {
905 Minus = "-",
906 Not = "not ",
907 Hash = "#",
908 #[cfg(feature = "lua53")]
909 Tilde = "~",
910 }, |other| panic!("unknown node {:?}", other))
911}
912
913fn hang_binop(ctx: &Context, binop: BinOp, shape: Shape, rhs: &Expression) -> BinOp {
918 let mut leading_comments = binop
922 .leading_comments()
923 .iter()
924 .flat_map(|x| {
925 vec![
926 create_newline_trivia(ctx),
927 create_indent_trivia(ctx, shape),
928 x.to_owned(),
929 ]
930 })
931 .collect::<Vec<_>>();
932
933 let mut trailing_comments = binop.trailing_comments();
935 leading_comments.append(&mut trailing_comments);
936
937 let mut expression_leading_comments = rhs
939 .leading_comments()
940 .iter()
941 .flat_map(|x| {
942 vec![
943 create_newline_trivia(ctx),
944 create_indent_trivia(ctx, shape),
945 x.to_owned(),
946 ]
947 })
948 .collect::<Vec<_>>();
949 leading_comments.append(&mut expression_leading_comments);
950
951 leading_comments.push(create_newline_trivia(ctx));
953 leading_comments.push(create_indent_trivia(ctx, shape));
954
955 binop.update_trivia(
956 FormatTriviaType::Replace(leading_comments),
957 FormatTriviaType::Replace(vec![Token::new(TokenType::spaces(1))]),
958 )
959}
960
961fn binop_expression_length(expression: &Expression, top_binop: &BinOp) -> usize {
963 match expression {
964 Expression::BinaryOperator { lhs, binop, rhs } => {
965 if binop.precedence() >= top_binop.precedence()
966 && binop.is_right_associative() == top_binop.is_right_associative()
967 {
968 if binop.is_right_associative() {
969 binop_expression_length(rhs, top_binop)
970 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**lhs).to_string().len()
972 } else {
973 binop_expression_length(lhs, top_binop)
974 + strip_trivia(binop).to_string().len() + 2 + strip_trivia(&**rhs).to_string().len()
976 }
977 } else {
978 0
979 }
980 }
981 _ => strip_trivia(expression).to_string().len(),
982 }
983}
984
985fn binop_expression_contains_comments(expression: &Expression, top_binop: &BinOp) -> bool {
986 match expression {
987 Expression::BinaryOperator { lhs, binop, rhs } => {
988 if binop.precedence() == top_binop.precedence() {
989 contains_comments(binop)
990 || rhs.has_leading_comments(CommentSearch::All)
991 || lhs.has_trailing_comments(CommentSearch::All)
992 || binop_expression_contains_comments(lhs, top_binop)
993 || binop_expression_contains_comments(rhs, top_binop)
994 } else {
995 false
996 }
997 }
998 _ => false,
999 }
1000}
1001
1002trait ToRange {
1004 fn to_range(&self) -> (usize, usize);
1005}
1006
1007impl ToRange for (usize, usize) {
1008 fn to_range(&self) -> (usize, usize) {
1009 *self
1010 }
1011}
1012
1013impl ToRange for Expression {
1014 fn to_range(&self) -> (usize, usize) {
1015 let (start, end) = self.range().unwrap();
1016 (start.bytes(), end.bytes())
1017 }
1018}
1019
1020#[derive(Clone, Copy, Debug)]
1046struct LeftmostRangeHang {
1047 range: (usize, usize),
1048 original_additional_indent_level: usize,
1049}
1050
1051impl LeftmostRangeHang {
1052 fn find(expression: &Expression, original_additional_indent_level: usize) -> Self {
1055 match expression {
1056 Expression::BinaryOperator { lhs, .. } => {
1057 Self::find(lhs, original_additional_indent_level)
1058 }
1059 _ => Self {
1060 range: expression.to_range(),
1061 original_additional_indent_level,
1062 },
1063 }
1064 }
1065
1066 fn required_shape<T: ToRange>(&self, shape: Shape, item: &T) -> Shape {
1070 let (expression_start, expression_end) = item.to_range();
1071 let (lhs_start, lhs_end) = self.range;
1072
1073 if lhs_start >= expression_start && lhs_end <= expression_end {
1074 shape.with_indent(
1075 shape
1076 .indent()
1077 .with_additional_indent(self.original_additional_indent_level),
1078 )
1079 } else {
1080 shape
1081 }
1082 }
1083}
1084
1085fn is_hang_binop_over_width(
1086 shape: Shape,
1087 expression: &Expression,
1088 top_binop: &BinOp,
1089 lhs_range: Option<LeftmostRangeHang>,
1090) -> bool {
1091 let shape = if let Some(lhs_hang) = lhs_range {
1092 lhs_hang.required_shape(shape, expression)
1093 } else {
1094 shape
1095 };
1096
1097 shape
1098 .add_width(binop_expression_length(expression, top_binop))
1099 .over_budget()
1100}
1101
1102fn binop_precedence_level(expression: &Expression) -> u8 {
1104 match expression {
1105 Expression::BinaryOperator { binop, .. } => binop.precedence(),
1106 _ => 0,
1107 }
1108}
1109
1110fn did_hang_expression(expression: &Expression) -> bool {
1111 if let Expression::BinaryOperator { binop, .. } = expression {
1112 binop
1115 .surrounding_trivia()
1116 .0
1117 .iter()
1118 .any(|x| trivia_is_newline(x))
1119 } else {
1120 false
1121 }
1122}
1123
1124#[derive(Debug)]
1125enum ExpressionSide {
1126 Left,
1127 Right,
1128}
1129
1130fn hang_binop_expression(
1131 ctx: &Context,
1132 expression: Expression,
1133 top_binop: BinOp,
1134 shape: Shape,
1135 lhs_range: Option<LeftmostRangeHang>,
1136 expression_context: ExpressionContext,
1137) -> Expression {
1138 const SPACE_LEN: usize = " ".len();
1139
1140 let full_expression = expression.to_owned();
1141
1142 match expression {
1143 Expression::BinaryOperator { lhs, binop, rhs } => {
1144 let same_op_level = binop.precedence() == top_binop.precedence()
1147 && binop.is_right_associative() == top_binop.is_right_associative();
1148 let is_right_associative = binop.is_right_associative();
1149
1150 let test_shape = if same_op_level {
1151 shape
1152 } else {
1153 shape.increment_additional_indent()
1154 };
1155
1156 let side_to_hang = if is_right_associative {
1157 ExpressionSide::Right
1158 } else {
1159 ExpressionSide::Left
1160 };
1161
1162 let over_column_width =
1164 is_hang_binop_over_width(test_shape, &full_expression, &binop, lhs_range);
1165 let should_hang = same_op_level
1166 || over_column_width
1167 || binop_expression_contains_comments(&full_expression, &binop);
1168
1169 let shape = if should_hang { test_shape } else { shape };
1171
1172 let mut new_binop = format_binop(ctx, &binop, shape);
1173 if should_hang {
1174 new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs);
1175 }
1176
1177 let (lhs, rhs) = match should_hang {
1178 true => {
1179 let lhs_shape = shape;
1180 let rhs_shape =
1181 shape.reset() + strip_trivia(&new_binop).to_string().len() + SPACE_LEN;
1182
1183 let (lhs, rhs) = match side_to_hang {
1184 ExpressionSide::Left => (
1185 hang_binop_expression(
1186 ctx,
1187 *lhs,
1188 if same_op_level {
1189 top_binop
1190 } else {
1191 binop.clone()
1192 },
1193 lhs_shape,
1194 lhs_range,
1195 expression_context,
1196 ),
1197 if contains_comments(&*rhs) {
1198 hang_binop_expression(
1199 ctx,
1200 *rhs,
1201 binop,
1202 shape,
1203 lhs_range,
1204 expression_context,
1205 )
1206 } else {
1207 format_expression_internal(
1208 ctx,
1209 &rhs,
1210 ExpressionContext::UnaryOrBinary,
1211 rhs_shape,
1212 )
1213 },
1214 ),
1215 ExpressionSide::Right => (
1216 if contains_comments(&*lhs) {
1217 hang_binop_expression(
1218 ctx,
1219 *lhs,
1220 binop.clone(),
1221 shape,
1222 lhs_range,
1223 expression_context,
1224 )
1225 } else {
1226 let context = if let BinOp::Caret(_) = binop {
1227 ExpressionContext::BinaryLHSExponent
1228 } else {
1229 ExpressionContext::BinaryLHS
1230 };
1231 format_expression_internal(ctx, &lhs, context, lhs_shape)
1232 },
1233 hang_binop_expression(
1234 ctx,
1235 *rhs,
1236 if same_op_level { top_binop } else { binop },
1237 rhs_shape,
1238 lhs_range,
1239 expression_context,
1240 ),
1241 ),
1242 };
1243 (
1244 lhs,
1245 rhs.update_leading_trivia(FormatTriviaType::Replace(Vec::new())),
1246 )
1247 }
1248 false => {
1249 let lhs = if contains_comments(&*lhs) {
1252 hang_binop_expression(
1253 ctx,
1254 *lhs,
1255 binop.to_owned(),
1256 shape,
1257 lhs_range,
1258 expression_context,
1259 )
1260 } else {
1261 let context = if let BinOp::Caret(_) = binop {
1262 ExpressionContext::BinaryLHSExponent
1263 } else {
1264 ExpressionContext::BinaryLHS
1265 };
1266 format_expression_internal(ctx, &lhs, context, shape)
1267 };
1268
1269 let rhs = if contains_comments(&*rhs) {
1270 hang_binop_expression(
1271 ctx,
1272 *rhs,
1273 binop,
1274 shape,
1275 lhs_range,
1276 expression_context,
1277 )
1278 } else {
1279 format_expression_internal(
1280 ctx,
1281 &rhs,
1282 ExpressionContext::UnaryOrBinary,
1283 shape,
1284 )
1285 };
1286
1287 (lhs, rhs)
1288 }
1289 };
1290
1291 Expression::BinaryOperator {
1292 lhs: Box::new(lhs),
1293 binop: new_binop,
1294 rhs: Box::new(rhs),
1295 }
1296 }
1297 _ => format_hanging_expression_(ctx, &expression, shape, expression_context, lhs_range),
1299 }
1300}
1301
1302fn format_hanging_expression_(
1304 ctx: &Context,
1305 expression: &Expression,
1306 shape: Shape,
1307 expression_context: ExpressionContext,
1308 lhs_range: Option<LeftmostRangeHang>,
1309) -> Expression {
1310 let expression_range = expression.to_range();
1311
1312 match expression {
1313 #[cfg(feature = "luau")]
1314 Expression::TypeAssertion {
1315 expression,
1316 type_assertion,
1317 } => {
1318 let (expression_context, value_shape) = (
1321 ExpressionContext::TypeAssertion,
1322 shape.take_first_line(&strip_trivia(type_assertion)),
1323 );
1324
1325 let expression = format_hanging_expression_(
1326 ctx,
1327 expression,
1328 value_shape,
1329 expression_context,
1330 lhs_range,
1331 );
1332
1333 #[cfg(feature = "luau")]
1335 let assertion_shape = shape.take_last_line(&expression);
1336
1337 Expression::TypeAssertion {
1338 expression: Box::new(expression),
1339 type_assertion: format_type_assertion(ctx, type_assertion, assertion_shape),
1340 }
1341 }
1342 Expression::Parentheses {
1343 contained,
1344 expression,
1345 } => {
1346 let lhs_shape = if let Some(lhs_hang) = lhs_range {
1347 lhs_hang.required_shape(shape, &expression_range)
1348 } else {
1349 shape
1350 };
1351 #[cfg(feature = "luau")]
1352 let keep_parentheses = matches!(
1353 expression_context,
1354 ExpressionContext::Prefix | ExpressionContext::TypeAssertion
1355 );
1356 #[cfg(not(feature = "luau"))]
1357 let keep_parentheses = matches!(expression_context, ExpressionContext::Prefix);
1358
1359 let use_internal_expression = check_excess_parentheses(expression, expression_context);
1362
1363 if use_internal_expression && !keep_parentheses {
1365 let (leading_comments, trailing_comments) =
1366 contained_span_comments(ctx, contained, shape);
1367 format_hanging_expression_(
1368 ctx,
1369 expression,
1370 lhs_shape,
1371 expression_context,
1372 lhs_range,
1373 )
1374 .update_leading_trivia(FormatTriviaType::Append(leading_comments))
1375 .update_trailing_trivia(FormatTriviaType::Append(trailing_comments))
1376 } else {
1377 let contained = format_contained_span(ctx, contained, lhs_shape);
1378
1379 let formatted_expression = format_expression(ctx, expression, lhs_shape + 1); let expression_str = formatted_expression.to_string();
1384 if !contains_comments(expression)
1385 && !lhs_shape.add_width(2 + expression_str.len()).over_budget()
1386 {
1387 return Expression::Parentheses {
1389 contained,
1390 expression: Box::new(formatted_expression),
1391 };
1392 }
1393
1394 let expression_shape = lhs_shape.reset().increment_additional_indent();
1396
1397 let (start_token, end_token) = contained.tokens();
1399
1400 let contained = ContainedSpan::new(
1403 start_token.update_trailing_trivia(FormatTriviaType::Append(vec![
1404 create_newline_trivia(ctx),
1405 create_indent_trivia(ctx, expression_shape),
1406 ])),
1407 end_token.update_leading_trivia(FormatTriviaType::Append(vec![
1408 create_newline_trivia(ctx),
1409 create_indent_trivia(ctx, lhs_shape),
1410 ])),
1411 );
1412
1413 Expression::Parentheses {
1414 contained,
1415 expression: Box::new(format_hanging_expression_(
1416 ctx,
1417 expression,
1418 expression_shape,
1419 ExpressionContext::Standard,
1420 None,
1421 )),
1422 }
1423 }
1424 }
1425 Expression::UnaryOperator { unop, expression } => {
1426 let unop = format_unop(ctx, unop, shape);
1427 let shape = shape + strip_leading_trivia(&unop).to_string().len();
1428 let expression = format_hanging_expression_(
1429 ctx,
1430 expression,
1431 shape,
1432 ExpressionContext::UnaryOrBinary,
1433 lhs_range,
1434 );
1435
1436 Expression::UnaryOperator {
1437 unop,
1438 expression: Box::new(expression),
1439 }
1440 }
1441 Expression::BinaryOperator { lhs, binop, rhs } => {
1442 let lhs = hang_binop_expression(
1444 ctx,
1445 *lhs.to_owned(),
1446 binop.to_owned(),
1447 shape,
1448 lhs_range,
1449 ExpressionContext::UnaryOrBinary,
1450 );
1451
1452 let current_shape = shape.take_last_line(&lhs) + 1; let mut new_binop = format_binop(ctx, binop, current_shape);
1454
1455 let singleline_shape = current_shape + strip_trivia(binop).to_string().len() + 1; let mut new_rhs = hang_binop_expression(
1458 ctx,
1459 *rhs.to_owned(),
1460 binop.to_owned(),
1461 singleline_shape,
1462 None,
1463 ExpressionContext::Standard,
1464 );
1465
1466 if (did_hang_expression(&lhs) && binop_precedence_level(&lhs) >= binop.precedence())
1468 || (did_hang_expression(&new_rhs)
1469 && binop_precedence_level(&new_rhs) >= binop.precedence())
1470 || contains_comments(binop)
1471 || lhs.has_trailing_comments(CommentSearch::All)
1472 || (shape.take_last_line(&lhs) + format!("{binop}{rhs}").len()).over_budget()
1473 {
1474 let hanging_shape = shape.reset() + strip_trivia(binop).to_string().len() + 1;
1475 new_binop = hang_binop(ctx, binop.to_owned(), shape, rhs);
1476 new_rhs = hang_binop_expression(
1477 ctx,
1478 *rhs.to_owned(),
1479 binop.to_owned(),
1480 hanging_shape,
1481 None,
1482 ExpressionContext::Standard,
1483 )
1484 .update_leading_trivia(FormatTriviaType::Replace(Vec::new()));
1485 }
1486
1487 Expression::BinaryOperator {
1488 lhs: Box::new(lhs),
1489 binop: new_binop,
1490 rhs: Box::new(new_rhs),
1491 }
1492 }
1493 _ => {
1494 let value_shape = if let Some(lhs_hang) = lhs_range {
1495 lhs_hang.required_shape(shape, &expression_range)
1496 } else {
1497 shape
1498 };
1499
1500 format_expression_internal(ctx, expression, expression_context, value_shape)
1501 }
1502 }
1503}
1504
1505pub fn hang_expression(
1506 ctx: &Context,
1507 expression: &Expression,
1508 shape: Shape,
1509 hang_level: Option<usize>,
1510) -> Expression {
1511 let original_additional_indent_level = shape.indent().additional_indent();
1512 let shape = match hang_level {
1513 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
1514 None => shape,
1515 };
1516
1517 let lhs_range =
1518 hang_level.map(|_| LeftmostRangeHang::find(expression, original_additional_indent_level));
1519
1520 format_hanging_expression_(
1521 ctx,
1522 expression,
1523 shape,
1524 ExpressionContext::Standard,
1525 lhs_range,
1526 )
1527}
1528
1529pub fn hang_expression_trailing_newline(
1530 ctx: &Context,
1531 expression: &Expression,
1532 shape: Shape,
1533 hang_level: Option<usize>,
1534) -> Expression {
1535 hang_expression(ctx, expression, shape, hang_level)
1536 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]))
1537}