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