1 use crate::{
2     check_should_format,
3     context::{create_indent_trivia, create_newline_trivia, Context},
4     formatters::{
5         trivia::{FormatTriviaType, UpdateTrailingTrivia},
6         trivia_util,
7     },
8     shape::Shape,
9     QuoteStyle,
10 };
11 use full_moon::ast::{
12     punctuated::{Pair, Punctuated},
13     span::ContainedSpan,
14 };
15 use full_moon::node::Node;
16 use full_moon::tokenizer::{StringLiteralQuoteType, Token, TokenKind, TokenReference, TokenType};
17 
18 #[derive(Debug)]
19 enum FormatTokenType {
20     Token,
21     LeadingTrivia,
22     TrailingTrivia,
23 }
24 
25 /// The type of end token being used to format
26 #[derive(Debug)]
27 pub enum EndTokenType {
28     /// A token ending a block, i.e. the `end` symbol
29     /// This means that the indent block it was closing is at the current block indent level
30     BlockEnd,
31     /// A closing brace at the end of a table.
32     /// This means that the indent block that it was closing is formed from an indent range, rather than the current block indent level.
33     ClosingBrace,
34     /// A closing parentheses at the end of e.g. a function call
35     /// This means that the indent block that it was closing is formed from an indent range, rather than the current block indent level.
36     ClosingParens,
37 }
38 
39 #[macro_export]
40 macro_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 
get_quote_to_use(ctx: &Context, literal: &str) -> StringLiteralQuoteType51 fn 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             // Check to see if there is a quote within it
63             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 
format_single_line_comment_string(comment: &str) -> &str79 fn format_single_line_comment_string(comment: &str) -> &str {
80     // Trim any trailing whitespace
81     comment.trim_end()
82 }
83 
84 /// Formats a Token Node
85 /// Also returns any extra leading or trailing trivia to add for the Token node
86 /// This should only ever be called from format_token_reference
format_token( ctx: &Context, token: &Token, format_type: &FormatTokenType, shape: Shape, ) -> (Token, Option<Vec<Token>>, Option<Vec<Token>>)87 fn format_token(
88     ctx: &Context,
89     token: &Token,
90     format_type: &FormatTokenType,
91     shape: Shape,
92 ) -> (Token, Option<Vec<Token>>, Option<Vec<Token>>) {
93     let mut leading_trivia: Option<Vec<Token>> = None;
94     let mut trailing_trivia: Option<Vec<Token>> = None;
95 
96     let token_type = match token.token_type() {
97         TokenType::Number { text } => {
98             let text = if text.starts_with('.') {
99                 String::from("0") + text.as_str()
100             } else if text.starts_with("-.") {
101                 String::from("-0") + text.get(1..).expect("unknown number literal")
102             } else {
103                 text.to_string()
104             }
105             .into();
106 
107             TokenType::Number { text }
108         }
109         TokenType::StringLiteral {
110             literal,
111             multi_line,
112             quote_type,
113         } => {
114             // If we have a brackets string, don't mess with it
115             if let StringLiteralQuoteType::Brackets = quote_type {
116                 TokenType::StringLiteral {
117                     literal: literal.to_owned(),
118                     multi_line: *multi_line,
119                     quote_type: StringLiteralQuoteType::Brackets,
120                 }
121             } else {
122                 // Match all escapes within the the string
123                 // Based off https://github.com/prettier/prettier/blob/181a325c1c07f1a4f3738665b7b28288dfb960bc/src/common/util.js#L439
124                 lazy_static::lazy_static! {
125                     static ref RE: regex::Regex = regex::Regex::new(r#"\\?(["'])|\\([\S\s])"#).unwrap();
126                     static ref UNNECESSARY_ESCAPES: regex::Regex = regex::Regex::new(r#"^[^\n\r"'0-9\\abfnrtuvxz]$"#).unwrap();
127                 }
128                 let quote_to_use = get_quote_to_use(ctx, literal);
129                 let literal = RE
130                     .replace_all(literal, |caps: &regex::Captures| {
131                         let quote = caps.get(1);
132                         let escaped = caps.get(2);
133 
134                         match quote {
135                             Some(quote) => {
136                                 // We have a quote, find what type it is, and see if we need to escape it
137                                 // then return the output string
138                                 match quote.as_str() {
139                                     "'" => {
140                                         // Check whether to escape the quote
141                                         if let StringLiteralQuoteType::Single = quote_to_use {
142                                             String::from("\\'")
143                                         } else {
144                                             String::from("'")
145                                         }
146                                     }
147                                     "\"" => {
148                                         // Check whether to escape the quote
149                                         if let StringLiteralQuoteType::Double = quote_to_use {
150                                             String::from("\\\"")
151                                         } else {
152                                             String::from("\"")
153                                         }
154                                     }
155                                     other => unreachable!("unknown quote type {:?}", other),
156                                 }
157                             }
158                             None => {
159                                 // We have a normal escape
160                                 // Test to see if it is necessary, and if not, then unescape it
161                                 let text = escaped
162                                     .expect("have a match which was neither an escape or a quote")
163                                     .as_str();
164                                 if UNNECESSARY_ESCAPES.is_match(text) {
165                                     text.to_owned()
166                                 } else {
167                                     format!("\\{}", text.to_owned())
168                                 }
169                             }
170                         }
171                     })
172                     .into();
173                 TokenType::StringLiteral {
174                     literal,
175                     multi_line: None,
176                     quote_type: quote_to_use,
177                 }
178             }
179         }
180         TokenType::SingleLineComment { comment } => {
181             let comment = format_single_line_comment_string(comment).into();
182 
183             match format_type {
184                 FormatTokenType::LeadingTrivia => {
185                     leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
186                     trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
187                 }
188                 FormatTokenType::TrailingTrivia => {
189                     // Add a space before the comment
190                     leading_trivia = Some(vec![Token::new(TokenType::spaces(1))]);
191                 }
192                 _ => (),
193             }
194 
195             TokenType::SingleLineComment { comment }
196         }
197         TokenType::MultiLineComment { blocks, comment } => {
198             if let FormatTokenType::LeadingTrivia = format_type {
199                 leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
200                 // Add a new line once the comment is completed
201                 trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
202             }
203 
204             TokenType::MultiLineComment {
205                 blocks: *blocks,
206                 comment: comment.to_owned(),
207             }
208         }
209         TokenType::Whitespace { characters } => TokenType::Whitespace {
210             characters: characters.to_owned(),
211         }, // TODO
212         _ => token.token_type().to_owned(),
213     };
214 
215     (Token::new(token_type), leading_trivia, trailing_trivia)
216 }
217 
218 /// Wraps around the format_token function to create a complete list of trivia to add to a node.
219 /// Handles any leading/trailing trivia provided by format_token, and appends it accordingly in relation to the formatted token.
220 /// Mainly useful for comments
221 /// Additional indent level will indent any trivia by the further level - useful for comments on the `end` token
load_token_trivia( ctx: &Context, current_trivia: Vec<&Token>, format_token_type: FormatTokenType, shape: Shape, ) -> Vec<Token>222 fn load_token_trivia(
223     ctx: &Context,
224     current_trivia: Vec<&Token>,
225     format_token_type: FormatTokenType,
226     shape: Shape,
227 ) -> Vec<Token> {
228     let mut token_trivia = Vec::new();
229 
230     let mut newline_count_in_succession = 0;
231     let mut trivia_iter = current_trivia.iter().peekable();
232 
233     while let Some(trivia) = trivia_iter.next() {
234         match trivia.token_type() {
235             TokenType::Whitespace { characters } => {
236                 // Handle cases where the user has left a newline gap in between e.g. two statements
237                 // If we are formatting trailing trivia, this can be ignored, as all trailing newlines will have already
238                 // been handled by the formatter.
239                 // If we are formatting leading trivia, we will allow a single newline to be kept in succession, if we
240                 // find one.
241                 match format_token_type {
242                     FormatTokenType::LeadingTrivia => {
243                         if characters.contains('\n') {
244                             newline_count_in_succession += 1;
245                             if newline_count_in_succession == 1 {
246                                 // We have a case where we will allow a single newline to be kept
247                                 token_trivia.push(create_newline_trivia(ctx));
248                             }
249                         }
250                     }
251                     FormatTokenType::TrailingTrivia => {
252                         // If the next trivia is a MultiLineComment, and this whitespace is just spacing, then we
253                         // will preserve a single space
254                         if let Some(next_trivia) = trivia_iter.peek() {
255                             if let TokenType::MultiLineComment { .. } = next_trivia.token_type() {
256                                 if !characters.contains('\n') {
257                                     token_trivia.push(Token::new(TokenType::spaces(1)))
258                                 }
259                             }
260                         }
261                     }
262                     _ => (),
263                 }
264 
265                 // Move to next trivia
266                 continue;
267             }
268             TokenType::SingleLineComment { .. } | TokenType::MultiLineComment { .. } => {
269                 // If we have a comment, when `format_token` is called, it will put a newline at the end
270                 // If this happens, we want to skip the next iteration if its a newline, as that has already been covered here
271                 if let FormatTokenType::LeadingTrivia = format_token_type {
272                     if let Some(next_trivia) = trivia_iter.peek() {
273                         if let TokenType::Whitespace { characters } = next_trivia.token_type() {
274                             if characters.contains('\n') {
275                                 // Consume iterator once to skip the next iteration
276                                 trivia_iter.next();
277                             }
278                         }
279                     }
280                 }
281                 // We will reset the counter as well, because the newline above is only to terminate the comment
282                 newline_count_in_succession = 0;
283             }
284             _ => {
285                 // Reset new line counter, as we only want two new lines in a row
286                 newline_count_in_succession = 0;
287             }
288         }
289 
290         let (token, leading_trivia, trailing_trivia) =
291             format_token(ctx, trivia.to_owned(), &format_token_type, shape);
292         if let Some(mut trivia) = leading_trivia {
293             token_trivia.append(&mut trivia);
294         }
295 
296         token_trivia.push(token);
297 
298         if let Some(mut trivia) = trailing_trivia {
299             token_trivia.append(&mut trivia)
300         }
301     }
302 
303     token_trivia
304 }
305 
format_token_reference( ctx: &Context, token_reference: &TokenReference, shape: Shape, ) -> TokenReference306 pub fn format_token_reference(
307     ctx: &Context,
308     token_reference: &TokenReference,
309     shape: Shape,
310 ) -> TokenReference {
311     // Preserve comments in leading/trailing trivia
312     let formatted_leading_trivia: Vec<Token> = load_token_trivia(
313         ctx,
314         token_reference.leading_trivia().collect(),
315         FormatTokenType::LeadingTrivia,
316         shape,
317     );
318     let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
319         ctx,
320         token_reference.trailing_trivia().collect(),
321         FormatTokenType::TrailingTrivia,
322         shape,
323     );
324 
325     let (token, _leading_trivia, _trailing_trivia) =
326         format_token(ctx, token_reference.token(), &FormatTokenType::Token, shape);
327 
328     TokenReference::new(formatted_leading_trivia, token, formatted_trailing_trivia)
329 }
330 // Formats a punctuation for a Punctuated sequence
331 // Removes any trailing comments to be stored in a comments buffer
format_punctuation(punctuation: &TokenReference) -> (TokenReference, Vec<Token>)332 pub fn format_punctuation(punctuation: &TokenReference) -> (TokenReference, Vec<Token>) {
333     let trailing_comments = punctuation
334         .trailing_trivia()
335         .filter(|x| trivia_util::trivia_is_comment(x))
336         .map(|x| {
337             // Prepend a single space beforehand
338             vec![Token::new(TokenType::spaces(1)), x.to_owned()]
339         })
340         .flatten()
341         .collect();
342 
343     (
344         TokenReference::new(
345             Vec::new(),
346             punctuation.token().to_owned(),
347             vec![Token::new(TokenType::spaces(1))], // Single space whitespace
348         ),
349         trailing_comments,
350     )
351 }
352 
353 // Formats a Punctuated sequence with correct punctuated values
354 // If there are any comments in between tied to the punctuation, they will be removed and stored in a returned comments buffer
format_punctuated_buffer<T, F>( ctx: &Context, old: &Punctuated<T>, shape: Shape, value_formatter: F, ) -> (Punctuated<T>, Vec<Token>) where T: std::fmt::Display, F: Fn(&Context, &T, Shape) -> T,355 pub fn format_punctuated_buffer<T, F>(
356     ctx: &Context,
357     old: &Punctuated<T>,
358     shape: Shape,
359     value_formatter: F,
360 ) -> (Punctuated<T>, Vec<Token>)
361 where
362     T: std::fmt::Display,
363     F: Fn(&Context, &T, Shape) -> T,
364 {
365     let mut formatted: Punctuated<T> = Punctuated::new();
366     let mut comments_buffer = Vec::new();
367     let mut shape = shape;
368 
369     for pair in old.pairs() {
370         match pair {
371             Pair::Punctuated(value, punctuation) => {
372                 // Format punctuation and store any comments into buffer
373                 let (formatted_punctuation, mut comments) = format_punctuation(punctuation);
374                 comments_buffer.append(&mut comments);
375 
376                 let formatted_value = value_formatter(ctx, value, shape);
377                 shape = shape + (formatted_value.to_string().len() + 2); // 2 = ", "
378 
379                 formatted.push(Pair::new(formatted_value, Some(formatted_punctuation)));
380             }
381             Pair::End(value) => {
382                 let formatted_value = value_formatter(ctx, value, shape);
383                 formatted.push(Pair::new(formatted_value, None));
384             }
385         }
386     }
387 
388     (formatted, comments_buffer)
389 }
390 
391 /// Formats a Punctuated sequence with correct punctuated values.
392 /// This function assumes that there are no comments present which would lead to a syntax error if the list was collapsed.
393 /// If not sure about comments, [`try_format_punctuated`] should be used instead.
format_punctuated<T, F>( ctx: &Context, old: &Punctuated<T>, shape: Shape, value_formatter: F, ) -> Punctuated<T> where T: std::fmt::Display, F: Fn(&Context, &T, Shape) -> T,394 pub fn format_punctuated<T, F>(
395     ctx: &Context,
396     old: &Punctuated<T>,
397     shape: Shape,
398     value_formatter: F,
399 ) -> Punctuated<T>
400 where
401     T: std::fmt::Display,
402     F: Fn(&Context, &T, Shape) -> T,
403 {
404     let mut list: Punctuated<T> = Punctuated::new();
405     let mut shape = shape;
406 
407     for pair in old.pairs() {
408         match pair {
409             Pair::Punctuated(value, punctuation) => {
410                 let value = value_formatter(ctx, value, shape);
411                 let punctuation = fmt_symbol!(ctx, punctuation, ", ", shape);
412                 shape = shape + (value.to_string().len() + 2); // 2 = ", "
413 
414                 list.push(Pair::new(value, Some(punctuation)));
415             }
416             Pair::End(value) => {
417                 let value = value_formatter(ctx, value, shape);
418                 list.push(Pair::new(value, None));
419             }
420         }
421     }
422 
423     list
424 }
425 
426 // Formats a Punctuated sequence across multiple lines. Also indents each item by hang_level
format_punctuated_multiline<T, F>( ctx: &Context, old: &Punctuated<T>, shape: Shape, value_formatter: F, hang_level: Option<usize>, ) -> Punctuated<T> where T: Node, F: Fn(&Context, &T, Shape) -> T,427 pub fn format_punctuated_multiline<T, F>(
428     ctx: &Context,
429     old: &Punctuated<T>,
430     shape: Shape,
431     value_formatter: F,
432     hang_level: Option<usize>,
433 ) -> Punctuated<T>
434 where
435     T: Node,
436     F: Fn(&Context, &T, Shape) -> T,
437 {
438     let mut formatted: Punctuated<T> = Punctuated::new();
439 
440     // Include hang level if required
441     let hanging_shape = match hang_level {
442         Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
443         None => shape,
444     };
445 
446     for (idx, pair) in old.pairs().enumerate() {
447         // Indent the pair (unless its the first item)
448         let shape = if idx == 0 {
449             shape
450         } else {
451             hanging_shape.reset()
452         };
453 
454         match pair {
455             Pair::Punctuated(value, punctuation) => {
456                 let value = value_formatter(ctx, value, shape);
457                 let punctuation = fmt_symbol!(ctx, punctuation, ",", shape).update_trailing_trivia(
458                     FormatTriviaType::Append(vec![
459                         create_newline_trivia(ctx),
460                         create_indent_trivia(ctx, hanging_shape), // Use hanging_shape here as we want to have a hanging indent for the next item, which may not be present in the shape of the first item.
461                     ]),
462                 );
463                 formatted.push(Pair::new(value, Some(punctuation)));
464             }
465             Pair::End(value) => {
466                 let formatted_value = value_formatter(ctx, value, shape);
467                 formatted.push(Pair::new(formatted_value, None));
468             }
469         }
470     }
471 
472     formatted
473 }
474 
475 /// Formats a Punctuated sequence, depending on its layout. If the sequence contains comments, we will format
476 /// across multiple lines
try_format_punctuated<T, F>( ctx: &Context, old: &Punctuated<T>, shape: Shape, value_formatter: F, hang_level: Option<usize>, ) -> Punctuated<T> where T: Node + std::fmt::Display, F: Fn(&Context, &T, Shape) -> T,477 pub fn try_format_punctuated<T, F>(
478     ctx: &Context,
479     old: &Punctuated<T>,
480     shape: Shape,
481     value_formatter: F,
482     hang_level: Option<usize>,
483 ) -> Punctuated<T>
484 where
485     T: Node + std::fmt::Display,
486     F: Fn(&Context, &T, Shape) -> T,
487 {
488     let mut format_multiline = false;
489 
490     for pair in old.pairs() {
491         if let Pair::Punctuated(_, punctuation) = pair {
492             if trivia_util::contains_comments(punctuation) {
493                 format_multiline = true;
494                 break;
495             }
496         }
497     }
498 
499     if format_multiline {
500         format_punctuated_multiline(ctx, old, shape, value_formatter, hang_level)
501     } else {
502         format_punctuated(ctx, old, shape, value_formatter)
503     }
504 }
505 
format_contained_span( ctx: &Context, contained_span: &ContainedSpan, shape: Shape, ) -> ContainedSpan506 pub fn format_contained_span(
507     ctx: &Context,
508     contained_span: &ContainedSpan,
509     shape: Shape,
510 ) -> ContainedSpan {
511     let (start_token, end_token) = contained_span.tokens();
512 
513     ContainedSpan::new(
514         format_token_reference(ctx, start_token, shape),
515         format_token_reference(ctx, end_token, shape),
516     )
517 }
518 
519 /// Formats a special TokenReference which is a symbol
520 /// Used to preserve the comments around the symbol
format_symbol( ctx: &Context, current_symbol: &TokenReference, wanted_symbol: &TokenReference, shape: Shape, ) -> TokenReference521 pub fn format_symbol(
522     ctx: &Context,
523     current_symbol: &TokenReference,
524     wanted_symbol: &TokenReference,
525     shape: Shape,
526 ) -> TokenReference {
527     // Preserve comments in leading/trailing trivia
528     let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
529         ctx,
530         current_symbol.leading_trivia().collect(),
531         FormatTokenType::LeadingTrivia,
532         shape,
533     );
534     let mut formatted_trailing_trivia: Vec<Token> = load_token_trivia(
535         ctx,
536         current_symbol.trailing_trivia().collect(),
537         FormatTokenType::TrailingTrivia,
538         shape,
539     );
540 
541     // Add on any whitespace created in the new symbol
542     // The wanted leading trivia should be added to the end of formatted_leading_trivia
543     // whilst the wanted trailing trivia should be added to the start of formatted_trailing_trivia
544     // so that the token is "wrapped" around
545     let mut wanted_leading_trivia: Vec<Token> = wanted_symbol
546         .leading_trivia()
547         .map(|x| x.to_owned())
548         .collect();
549     let mut wanted_trailing_trivia: Vec<Token> = wanted_symbol
550         .trailing_trivia()
551         .map(|x| x.to_owned())
552         .collect();
553     wanted_trailing_trivia.append(&mut formatted_trailing_trivia);
554     formatted_leading_trivia.append(&mut wanted_leading_trivia);
555 
556     TokenReference::new(
557         formatted_leading_trivia,
558         wanted_symbol.token().to_owned(),
559         wanted_trailing_trivia,
560     )
561 }
562 
563 /// Formats a token present at the end of an indented block, such as the `end` token or closing brace in a multiline table.
564 /// This is required due to leading comments bound to the last token - they need to have one level higher indentation
format_end_token( ctx: &Context, current_token: &TokenReference, _token_type: EndTokenType, shape: Shape, ) -> TokenReference565 pub fn format_end_token(
566     ctx: &Context,
567     current_token: &TokenReference,
568     _token_type: EndTokenType,
569     shape: Shape,
570 ) -> TokenReference {
571     // Indent any comments leading a token, as these comments are technically part of the function body block
572     let formatted_leading_trivia: Vec<Token> = load_token_trivia(
573         ctx,
574         current_token.leading_trivia().collect(),
575         FormatTokenType::LeadingTrivia,
576         // The indent level we are currently at is one less (as we are at the block closing token, not the indented block).
577         // The comment is present inside the indented block
578         shape.increment_additional_indent(),
579     );
580     let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
581         ctx,
582         current_token.trailing_trivia().collect(),
583         FormatTokenType::TrailingTrivia,
584         shape,
585     );
586 
587     // Special case for block end tokens:
588     // We will reverse the leading trivia, and keep removing any newlines we find, until we find something else, then we stop.
589     // This is to remove unnecessary newlines at the end of the block.
590     let mut iter = formatted_leading_trivia.iter().rev().peekable();
591 
592     let mut formatted_leading_trivia = Vec::new();
593     let mut stop_removal = false;
594     while let Some(x) = iter.next() {
595         match x.token_type() {
596             TokenType::Whitespace { ref characters } => {
597                 if !stop_removal
598                     && characters.contains('\n')
599                     && !matches!(
600                         iter.peek().map(|x| x.token_kind()),
601                         Some(TokenKind::SingleLineComment) | Some(TokenKind::MultiLineComment)
602                     )
603                 {
604                     continue;
605                 } else {
606                     formatted_leading_trivia.push(x.to_owned());
607                 }
608             }
609             _ => {
610                 formatted_leading_trivia.push(x.to_owned());
611                 stop_removal = true; // Stop removing newlines once we have seen some sort of comment
612             }
613         }
614     }
615 
616     // Need to reverse the vector since we reversed the iterator
617     formatted_leading_trivia.reverse();
618 
619     TokenReference::new(
620         formatted_leading_trivia,
621         Token::new(current_token.token_type().to_owned()),
622         formatted_trailing_trivia,
623     )
624 }
625 
626 /// Continues mutating a Vec of Tokens until there is no more trailing whitespace present
pop_until_no_whitespace(trivia: &mut Vec<Token>)627 fn pop_until_no_whitespace(trivia: &mut Vec<Token>) {
628     if let Some(t) = trivia.pop() {
629         match t.token_kind() {
630             TokenKind::Whitespace => pop_until_no_whitespace(trivia), // Keep popping until no more whitespace
631             _ => trivia.push(t), // Its not whitespace, so add it back and stop popping
632         }
633     }
634 }
635 
636 /// Format the EOF token.
637 /// This is done by removing any leading whitespace, whilst preserving leading comments.
638 /// An EOF token has no trailing trivia
format_eof(ctx: &Context, eof: &TokenReference, shape: Shape) -> TokenReference639 pub fn format_eof(ctx: &Context, eof: &TokenReference, shape: Shape) -> TokenReference {
640     check_should_format!(ctx, eof);
641 
642     // Need to preserve any comments in leading_trivia if present
643     let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
644         ctx,
645         eof.leading_trivia().collect(),
646         FormatTokenType::LeadingTrivia,
647         shape,
648     );
649 
650     let only_whitespace = formatted_leading_trivia
651         .iter()
652         .all(|x| x.token_kind() == TokenKind::Whitespace);
653     if only_whitespace {
654         // Remove all the whitespace, and return an empty EOF
655         TokenReference::new(Vec::new(), Token::new(TokenType::Eof), Vec::new())
656     } else {
657         // We have some comments in here, so we need to remove any trailing whitespace then add a single new line
658         pop_until_no_whitespace(&mut formatted_leading_trivia);
659         formatted_leading_trivia.push(create_newline_trivia(ctx));
660 
661         TokenReference::new(
662             formatted_leading_trivia,
663             Token::new(TokenType::Eof),
664             Vec::new(),
665         )
666     }
667 }
668