1#[cfg(feature = "cfxlua")]
2use crate::LuaVersion;
3use crate::{
4 context::{
5 create_indent_trivia, create_newline_trivia, line_ending_character, Context, FormatNode,
6 },
7 formatters::{
8 trivia::{FormatTriviaType, UpdateLeadingTrivia, UpdateTrailingTrivia, UpdateTrivia},
9 trivia_util::{
10 self, punctuated_inline_comments, CommentSearch, GetLeadingTrivia, GetTrailingTrivia,
11 HasInlineComments,
12 },
13 },
14 shape::Shape,
15 QuoteStyle,
16};
17use full_moon::node::Node;
18use full_moon::tokenizer::{StringLiteralQuoteType, Token, TokenKind, TokenReference, TokenType};
19use full_moon::{
20 ast::{
21 punctuated::{Pair, Punctuated},
22 span::ContainedSpan,
23 },
24 ShortString,
25};
26
27#[derive(Debug, Clone, Copy)]
28pub enum FormatTokenType {
29 Token,
30 LeadingTrivia,
31 TrailingTrivia,
32}
33
34#[derive(Debug)]
36pub enum EndTokenType {
37 IndentComments,
40 InlineComments,
42}
43
44#[macro_export]
45macro_rules! fmt_symbol {
46 ($ctx:expr, $token:expr, $x:expr, $shape:expr) => {
47 $crate::formatters::general::format_symbol(
48 $ctx,
49 $token,
50 &TokenReference::symbol_specific_lua_version($x, $ctx.config().syntax.into()).unwrap(),
51 $shape,
52 )
53 };
54}
55
56fn get_quote_to_use(ctx: &Context, literal: &str) -> StringLiteralQuoteType {
57 match ctx.config().quote_style {
58 QuoteStyle::ForceDouble => StringLiteralQuoteType::Double,
59 QuoteStyle::ForceSingle => StringLiteralQuoteType::Single,
60 _ => {
61 let preferred = match ctx.config().quote_style {
62 QuoteStyle::AutoPreferDouble => StringLiteralQuoteType::Double,
63 QuoteStyle::AutoPreferSingle => StringLiteralQuoteType::Single,
64 _ => unreachable!("have other quote styles we haven't looked into yet"),
65 };
66
67 if literal.contains('\'') || literal.contains('"') {
69 let num_single_quotes = literal.matches('\'').count();
70 let num_double_quotes = literal.matches('"').count();
71
72 match num_single_quotes.cmp(&num_double_quotes) {
73 std::cmp::Ordering::Equal => preferred,
74 std::cmp::Ordering::Greater => StringLiteralQuoteType::Double,
75 std::cmp::Ordering::Less => StringLiteralQuoteType::Single,
76 }
77 } else {
78 preferred
79 }
80 }
81 }
82}
83
84fn format_single_line_comment_string(comment: &str) -> &str {
85 comment.trim_end()
87}
88
89pub fn trivia_to_vec(
90 (token, leading, trailing): (Token, Option<Vec<Token>>, Option<Vec<Token>>),
91) -> Vec<Token> {
92 let mut output = leading.unwrap_or_default();
93 output.push(token);
94 output.append(&mut trailing.unwrap_or_default());
95
96 output
97}
98
99fn format_string_literal(
100 ctx: &Context,
101 literal: &ShortString,
102 multi_line_depth: &usize,
103 quote_type: &StringLiteralQuoteType,
104) -> TokenType {
105 #[cfg(feature = "cfxlua")]
107 if ctx.config().syntax == LuaVersion::CfxLua
108 && matches!(quote_type, StringLiteralQuoteType::Backtick)
109 {
110 return TokenType::StringLiteral {
111 literal: literal.clone(),
112 multi_line_depth: *multi_line_depth,
113 quote_type: StringLiteralQuoteType::Backtick,
114 };
115 }
116
117 if let StringLiteralQuoteType::Brackets = quote_type {
119 let literal = literal
122 .replace("\r\n", "\n")
123 .replace('\n', &line_ending_character(ctx.config().line_endings));
124
125 TokenType::StringLiteral {
126 literal: literal.into(),
127 multi_line_depth: *multi_line_depth,
128 quote_type: StringLiteralQuoteType::Brackets,
129 }
130 } else {
131 lazy_static::lazy_static! {
134 static ref RE: regex::Regex = regex::Regex::new(r#"\\?(["'])|\\([\S\s])"#).unwrap();
135 static ref UNNECESSARY_ESCAPES: regex::Regex = regex::Regex::new(r#"^[^\n\r"'0-9\\abfnrtuvxz]$"#).unwrap();
136 }
137 let quote_to_use = get_quote_to_use(ctx, literal);
138 let literal = RE
139 .replace_all(literal, |caps: ®ex::Captures| {
140 let quote = caps.get(1);
141 let escaped = caps.get(2);
142
143 match quote {
144 Some(quote) => {
145 match quote.as_str() {
148 "'" => {
149 if let StringLiteralQuoteType::Single = quote_to_use {
151 String::from("\\'")
152 } else {
153 String::from("'")
154 }
155 }
156 "\"" => {
157 if let StringLiteralQuoteType::Double = quote_to_use {
159 String::from("\\\"")
160 } else {
161 String::from("\"")
162 }
163 }
164 other => unreachable!("unknown quote type {:?}", other),
165 }
166 }
167 None => {
168 let text = escaped
171 .expect("have a match which was neither an escape or a quote")
172 .as_str();
173 if UNNECESSARY_ESCAPES.is_match(text) {
174 text.to_owned()
175 } else {
176 format!("\\{}", text.to_owned())
177 }
178 }
179 }
180 })
181 .into();
182
183 TokenType::StringLiteral {
184 literal,
185 multi_line_depth: *multi_line_depth,
186 quote_type: quote_to_use,
187 }
188 }
189}
190
191pub fn format_token(
195 ctx: &Context,
196 token: &Token,
197 format_type: FormatTokenType,
198 shape: Shape,
199) -> (Token, Option<Vec<Token>>, Option<Vec<Token>>) {
200 let mut leading_trivia: Option<Vec<Token>> = None;
201 let mut trailing_trivia: Option<Vec<Token>> = None;
202
203 let token_type = match token.token_type() {
204 TokenType::Number { text } => {
205 let text = if text.starts_with('.') {
206 String::from("0") + text.as_str()
207 } else if text.starts_with("-.") {
208 String::from("-0") + text.get(1..).expect("unknown number literal")
209 } else {
210 text.to_string()
211 }
212 .into();
213
214 TokenType::Number { text }
215 }
216 TokenType::StringLiteral {
217 literal,
218 multi_line_depth,
219 quote_type,
220 } => format_string_literal(ctx, literal, multi_line_depth, quote_type),
221 TokenType::Shebang { line } => {
222 let line = format_single_line_comment_string(line).into();
223
224 debug_assert!(matches!(format_type, FormatTokenType::LeadingTrivia));
226 trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
227
228 TokenType::Shebang { line }
229 }
230 TokenType::SingleLineComment { comment } => {
231 let comment = format_single_line_comment_string(comment).into();
232
233 match format_type {
234 FormatTokenType::LeadingTrivia => {
235 leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
236 trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
237 }
238 FormatTokenType::TrailingTrivia => {
239 leading_trivia = Some(vec![Token::new(TokenType::spaces(1))]);
241 }
242 _ => (),
243 }
244
245 TokenType::SingleLineComment { comment }
246 }
247 TokenType::MultiLineComment { blocks, comment } => {
248 if let FormatTokenType::LeadingTrivia = format_type {
249 leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
250 trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
252 }
253
254 let comment = comment
257 .replace("\r\n", "\n")
258 .replace('\n', &line_ending_character(ctx.config().line_endings));
259
260 TokenType::MultiLineComment {
261 blocks: *blocks,
262 comment: comment.into(),
263 }
264 }
265 TokenType::Whitespace { characters } => TokenType::Whitespace {
266 characters: characters.to_owned(),
267 }, #[cfg(feature = "luau")]
269 TokenType::InterpolatedString { literal, kind } => TokenType::InterpolatedString {
270 literal: literal.to_owned(),
271 kind: kind.to_owned(),
272 },
273 _ => token.token_type().to_owned(),
274 };
275
276 (Token::new(token_type), leading_trivia, trailing_trivia)
277}
278
279fn load_token_trivia(
284 ctx: &Context,
285 current_trivia: Vec<&Token>,
286 format_token_type: FormatTokenType,
287 shape: Shape,
288) -> Vec<Token> {
289 let mut token_trivia = Vec::new();
290
291 let mut newline_count_in_succession = 0;
292 let mut trivia_iter = current_trivia.iter().peekable();
293
294 while let Some(trivia) = trivia_iter.next() {
295 match trivia.token_type() {
296 TokenType::Whitespace { characters } => {
297 match format_token_type {
303 FormatTokenType::LeadingTrivia => {
304 if characters.contains('\n') {
305 newline_count_in_succession += 1;
306 if newline_count_in_succession == 1 {
307 token_trivia.push(create_newline_trivia(ctx));
309 }
310 }
311 }
312 FormatTokenType::TrailingTrivia => {
313 if let Some(next_trivia) = trivia_iter.peek() {
316 if let TokenType::MultiLineComment { .. } = next_trivia.token_type() {
317 if !characters.contains('\n') {
318 token_trivia.push(Token::new(TokenType::spaces(1)))
319 }
320 }
321 }
322 }
323 _ => (),
324 }
325
326 continue;
328 }
329 TokenType::Shebang { .. }
330 | TokenType::SingleLineComment { .. }
331 | TokenType::MultiLineComment { .. } => {
332 if let FormatTokenType::LeadingTrivia = format_token_type {
335 if let Some(next_trivia) = trivia_iter.peek() {
336 if let TokenType::Whitespace { characters } = next_trivia.token_type() {
337 if characters.contains('\n') {
338 trivia_iter.next();
340 }
341 }
342 }
343 }
344 newline_count_in_succession = 0;
346 }
347 _ => {
348 newline_count_in_succession = 0;
350 }
351 }
352
353 let (token, leading_trivia, trailing_trivia) =
354 format_token(ctx, trivia.to_owned(), format_token_type, shape);
355 if let Some(mut trivia) = leading_trivia {
356 token_trivia.append(&mut trivia);
357 }
358
359 token_trivia.push(token);
360
361 if let Some(mut trivia) = trailing_trivia {
362 token_trivia.append(&mut trivia)
363 }
364 }
365
366 token_trivia
367}
368
369pub fn format_token_reference(
370 ctx: &Context,
371 token_reference: &TokenReference,
372 shape: Shape,
373) -> TokenReference {
374 let formatted_leading_trivia: Vec<Token> = load_token_trivia(
376 ctx,
377 token_reference.leading_trivia().collect(),
378 FormatTokenType::LeadingTrivia,
379 shape,
380 );
381 let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
382 ctx,
383 token_reference.trailing_trivia().collect(),
384 FormatTokenType::TrailingTrivia,
385 shape,
386 );
387
388 let (token, _leading_trivia, _trailing_trivia) =
389 format_token(ctx, token_reference.token(), FormatTokenType::Token, shape);
390
391 TokenReference::new(formatted_leading_trivia, token, formatted_trailing_trivia)
392}
393
394pub fn format_punctuated<T, F>(
398 ctx: &Context,
399 old: &Punctuated<T>,
400 shape: Shape,
401 value_formatter: F,
402) -> Punctuated<T>
403where
404 T: std::fmt::Display,
405 F: Fn(&Context, &T, Shape) -> T,
406{
407 let mut list: Punctuated<T> = Punctuated::new();
408 let mut shape = shape;
409
410 for pair in old.pairs() {
411 match pair {
412 Pair::Punctuated(value, punctuation) => {
413 let value = value_formatter(ctx, value, shape);
414 let punctuation = fmt_symbol!(ctx, punctuation, ",", shape).update_trailing_trivia(
415 FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))]),
416 );
417 shape = shape.take_last_line(&value) + 2; list.push(Pair::new(value, Some(punctuation)));
420 }
421 Pair::End(value) => {
422 let value = value_formatter(ctx, value, shape);
423 list.push(Pair::new(value, None));
424 }
425 }
426 }
427
428 list
429}
430
431pub fn format_punctuated_multiline<T, F>(
433 ctx: &Context,
434 old: &Punctuated<T>,
435 shape: Shape,
436 value_formatter: F,
437 hang_level: Option<usize>,
438) -> Punctuated<T>
439where
440 T: Node + GetLeadingTrivia + UpdateLeadingTrivia + GetTrailingTrivia + UpdateTrailingTrivia,
441 F: Fn(&Context, &T, Shape) -> T,
442{
443 let mut formatted: Punctuated<T> = Punctuated::new();
444
445 let hanging_shape = match hang_level {
447 Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
448 None => shape,
449 };
450
451 for (idx, pair) in old.pairs().enumerate() {
452 let shape = if idx == 0 {
454 shape
455 } else {
456 hanging_shape.reset()
457 };
458
459 match pair {
460 Pair::Punctuated(value, punctuation) => {
461 let value = value_formatter(ctx, value, shape);
462 let value = if idx == 0 {
463 value
464 } else {
465 trivia_util::prepend_newline_indent(ctx, &value, hanging_shape)
466 };
467
468 let mut trailing_comments = value.trailing_comments();
471 let value = value.update_trailing_trivia(FormatTriviaType::Replace(vec![]));
472
473 let punctuation = fmt_symbol!(ctx, punctuation, ",", shape);
474 trailing_comments.append(&mut punctuation.trailing_trivia().cloned().collect());
475 let punctuation = punctuation
476 .update_trailing_trivia(FormatTriviaType::Replace(trailing_comments));
477
478 formatted.push(Pair::new(value, Some(punctuation)));
479 }
480 Pair::End(value) => {
481 let value = value_formatter(ctx, value, shape);
482 let value = if idx == 0 {
483 value
484 } else {
485 trivia_util::prepend_newline_indent(ctx, &value, hanging_shape)
486 };
487 formatted.push(Pair::new(value, None));
488 }
489 }
490 }
491
492 formatted
493}
494
495pub fn try_format_punctuated<T, F>(
498 ctx: &Context,
499 old: &Punctuated<T>,
500 shape: Shape,
501 value_formatter: F,
502 hang_level: Option<usize>,
503) -> Punctuated<T>
504where
505 T: Node
506 + GetLeadingTrivia
507 + UpdateLeadingTrivia
508 + GetTrailingTrivia
509 + UpdateTrailingTrivia
510 + HasInlineComments
511 + std::fmt::Display,
512 F: Fn(&Context, &T, Shape) -> T,
513{
514 let format_multiline = punctuated_inline_comments(old, false);
517
518 if format_multiline {
519 format_punctuated_multiline(ctx, old, shape, value_formatter, hang_level)
520 } else {
521 format_punctuated(ctx, old, shape, value_formatter)
522 }
523}
524
525pub fn format_contained_punctuated_multiline<T, F1>(
528 ctx: &Context,
529 parentheses: &ContainedSpan,
530 arguments: &Punctuated<T>,
531 argument_formatter: F1, shape: Shape,
533) -> (ContainedSpan, Punctuated<T>)
534where
535 T: UpdateLeadingTrivia + GetTrailingTrivia + UpdateTrailingTrivia,
536 F1: Fn(&Context, &T, Shape) -> T,
537{
538 let (start_parens, end_parens) = parentheses.tokens();
540 let start_parens = format_token_reference(ctx, start_parens, shape)
541 .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]));
542
543 let end_parens = format_end_token(ctx, end_parens, EndTokenType::IndentComments, shape)
544 .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
545 ctx, shape,
546 )]));
547
548 let parentheses = ContainedSpan::new(start_parens, end_parens);
549
550 let mut formatted_arguments = Punctuated::new();
551 let shape = shape.increment_additional_indent();
552
553 for argument in arguments.pairs() {
554 let shape = shape.reset(); let formatted_argument = argument_formatter(ctx, argument.value(), shape)
557 .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
558 ctx, shape,
559 )]));
560
561 let multiline_comments =
564 formatted_argument.trailing_comments_search(CommentSearch::Multiline);
565 let singleline_comments =
566 formatted_argument.trailing_comments_search(CommentSearch::Single);
567
568 let formatted_argument = formatted_argument
569 .update_trailing_trivia(FormatTriviaType::Replace(multiline_comments));
570
571 let punctuation = match argument.punctuation() {
572 Some(punctuation) => {
573 let symbol = fmt_symbol!(ctx, punctuation, ",", shape);
576
577 let mut trailing_trivia: Vec<_> = symbol
578 .leading_trivia()
579 .filter(|trivia| trivia_util::trivia_is_comment(trivia))
580 .cloned()
581 .flat_map(|x| {
582 vec![
584 create_newline_trivia(ctx),
585 create_indent_trivia(ctx, shape),
586 x,
587 ]
588 })
589 .chain(singleline_comments)
590 .collect();
591 trailing_trivia.push(create_newline_trivia(ctx));
592
593 let symbol = symbol.update_trivia(
594 FormatTriviaType::Replace(vec![]),
595 FormatTriviaType::Append(trailing_trivia),
596 );
597
598 Some(symbol)
599 }
600 None => Some(TokenReference::new(
604 singleline_comments,
605 create_newline_trivia(ctx),
606 vec![],
607 )),
608 };
609
610 formatted_arguments.push(Pair::new(formatted_argument, punctuation))
611 }
612
613 (parentheses, formatted_arguments)
614}
615
616pub fn format_contained_span(
617 ctx: &Context,
618 contained_span: &ContainedSpan,
619 shape: Shape,
620) -> ContainedSpan {
621 let (start_token, end_token) = contained_span.tokens();
622
623 ContainedSpan::new(
624 format_token_reference(ctx, start_token, shape),
625 format_token_reference(ctx, end_token, shape),
626 )
627}
628
629pub fn format_symbol(
632 ctx: &Context,
633 current_symbol: &TokenReference,
634 wanted_symbol: &TokenReference,
635 shape: Shape,
636) -> TokenReference {
637 let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
639 ctx,
640 current_symbol.leading_trivia().collect(),
641 FormatTokenType::LeadingTrivia,
642 shape,
643 );
644 let mut formatted_trailing_trivia: Vec<Token> = load_token_trivia(
645 ctx,
646 current_symbol.trailing_trivia().collect(),
647 FormatTokenType::TrailingTrivia,
648 shape,
649 );
650
651 let mut wanted_leading_trivia: Vec<Token> = wanted_symbol
656 .leading_trivia()
657 .map(|x| x.to_owned())
658 .collect();
659 let mut wanted_trailing_trivia: Vec<Token> = wanted_symbol
660 .trailing_trivia()
661 .map(|x| x.to_owned())
662 .collect();
663 wanted_trailing_trivia.append(&mut formatted_trailing_trivia);
664 formatted_leading_trivia.append(&mut wanted_leading_trivia);
665
666 TokenReference::new(
667 formatted_leading_trivia,
668 wanted_symbol.token().to_owned(),
669 wanted_trailing_trivia,
670 )
671}
672
673pub fn format_end_token(
676 ctx: &Context,
677 current_token: &TokenReference,
678 token_type: EndTokenType,
679 shape: Shape,
680) -> TokenReference {
681 let formatted_leading_trivia: Vec<Token> = load_token_trivia(
683 ctx,
684 current_token.leading_trivia().collect(),
685 FormatTokenType::LeadingTrivia,
686 if let EndTokenType::IndentComments = token_type {
689 shape.increment_additional_indent()
690 } else {
691 shape
692 },
693 );
694 let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
695 ctx,
696 current_token.trailing_trivia().collect(),
697 FormatTokenType::TrailingTrivia,
698 shape,
699 );
700
701 let mut iter = formatted_leading_trivia.iter().rev().peekable();
705
706 let mut formatted_leading_trivia = Vec::new();
707 let mut stop_removal = false;
708 while let Some(x) = iter.next() {
709 match x.token_type() {
710 TokenType::Whitespace { ref characters } => {
711 if !stop_removal
712 && characters.contains('\n')
713 && !matches!(
714 iter.peek().map(|x| x.token_kind()),
715 Some(TokenKind::SingleLineComment) | Some(TokenKind::MultiLineComment)
716 )
717 {
718 continue;
719 } else {
720 formatted_leading_trivia.push(x.to_owned());
721 }
722 }
723 _ => {
724 formatted_leading_trivia.push(x.to_owned());
725 stop_removal = true; }
727 }
728 }
729
730 formatted_leading_trivia.reverse();
732
733 TokenReference::new(
734 formatted_leading_trivia,
735 Token::new(current_token.token_type().to_owned()),
736 formatted_trailing_trivia,
737 )
738}
739
740fn pop_until_no_whitespace(trivia: &mut Vec<Token>) {
742 if let Some(t) = trivia.pop() {
743 match t.token_kind() {
744 TokenKind::Whitespace => pop_until_no_whitespace(trivia), _ => trivia.push(t), }
747 }
748}
749
750pub fn format_eof(ctx: &Context, eof: &TokenReference, shape: Shape) -> TokenReference {
754 if ctx.should_format_node(eof) != FormatNode::Normal {
755 return eof.to_owned();
756 }
757
758 let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
760 ctx,
761 eof.leading_trivia().collect(),
762 FormatTokenType::LeadingTrivia,
763 shape,
764 );
765
766 let only_whitespace = formatted_leading_trivia
767 .iter()
768 .all(|x| x.token_kind() == TokenKind::Whitespace);
769 if only_whitespace {
770 TokenReference::new(Vec::new(), Token::new(TokenType::Eof), Vec::new())
772 } else {
773 pop_until_no_whitespace(&mut formatted_leading_trivia);
775 formatted_leading_trivia.push(create_newline_trivia(ctx));
776
777 TokenReference::new(
778 formatted_leading_trivia,
779 Token::new(TokenType::Eof),
780 Vec::new(),
781 )
782 }
783}