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: ®ex::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