stylua_lib/formatters/
general.rs

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/// The type of end token being used to format
30#[derive(Debug)]
31pub enum EndTokenType {
32    /// Indent the leading comments of the token.
33    /// This typically occurs when the token is used to close an indented block scope
34    IndentComments,
35    /// Keep the comments at the same level as the end token
36    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            // 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
79fn format_single_line_comment_string(comment: &str) -> &str {
80    // Trim any trailing whitespace
81    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
94/// Formats a Token Node
95/// Also returns any extra leading or trailing trivia to add for the Token node
96/// This should only ever be called from format_token_reference
97pub 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 we have a brackets string, don't mess with it
125            if let StringLiteralQuoteType::Brackets = quote_type {
126                // Convert the string to the correct line endings, by first normalising to LF
127                // then converting LF to output
128                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                // Match all escapes within the string
139                // Based off https://github.com/prettier/prettier/blob/181a325c1c07f1a4f3738665b7b28288dfb960bc/src/common/util.js#L439
140                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: &regex::Captures| {
147                        let quote = caps.get(1);
148                        let escaped = caps.get(2);
149
150                        match quote {
151                            Some(quote) => {
152                                // We have a quote, find what type it is, and see if we need to escape it
153                                // then return the output string
154                                match quote.as_str() {
155                                    "'" => {
156                                        // Check whether to escape the quote
157                                        if let StringLiteralQuoteType::Single = quote_to_use {
158                                            String::from("\\'")
159                                        } else {
160                                            String::from("'")
161                                        }
162                                    }
163                                    "\"" => {
164                                        // Check whether to escape the quote
165                                        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                                // We have a normal escape
176                                // Test to see if it is necessary, and if not, then unescape it
177                                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            // Shebang must always be leading trivia, as it is start of file. Terminate it with a newline
200            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                    // Add a space before the comment
215                    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                // Add a new line once the comment is completed
226                trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
227            }
228
229            // Convert the comment to the correct line endings, by first normalising to LF
230            // then converting LF to output
231            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        }, // TODO
243        #[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
254/// Wraps around the format_token function to create a complete list of trivia to add to a node.
255/// Handles any leading/trailing trivia provided by format_token, and appends it accordingly in relation to the formatted token.
256/// Mainly useful for comments
257/// Additional indent level will indent any trivia by the further level - useful for comments on the `end` token
258fn 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                // Handle cases where the user has left a newline gap in between e.g. two statements
273                // If we are formatting trailing trivia, this can be ignored, as all trailing newlines will have already
274                // been handled by the formatter.
275                // If we are formatting leading trivia, we will allow a single newline to be kept in succession, if we
276                // find one.
277                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                                // We have a case where we will allow a single newline to be kept
283                                token_trivia.push(create_newline_trivia(ctx));
284                            }
285                        }
286                    }
287                    FormatTokenType::TrailingTrivia => {
288                        // If the next trivia is a MultiLineComment, and this whitespace is just spacing, then we
289                        // will preserve a single space
290                        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                // Move to next trivia
302                continue;
303            }
304            TokenType::Shebang { .. }
305            | TokenType::SingleLineComment { .. }
306            | TokenType::MultiLineComment { .. } => {
307                // If we have a comment, when `format_token` is called, it will put a newline at the end
308                // If this happens, we want to skip the next iteration if its a newline, as that has already been covered here
309                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                                // Consume iterator once to skip the next iteration
314                                trivia_iter.next();
315                            }
316                        }
317                    }
318                }
319                // We will reset the counter as well, because the newline above is only to terminate the comment
320                newline_count_in_succession = 0;
321            }
322            _ => {
323                // Reset new line counter, as we only want two new lines in a row
324                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    // Preserve comments in leading/trailing trivia
350    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
369/// Formats a Punctuated sequence with correct punctuated values.
370/// This function assumes that there are no comments present which would lead to a syntax error if the list was collapsed.
371/// If not sure about comments, [`try_format_punctuated`] should be used instead.
372pub 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; // 2 = ", "
393
394                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
406// Formats a Punctuated sequence across multiple lines. Also indents each item by hang_level
407pub 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    // Include hang level if required
421    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        // Indent the pair (unless its the first item)
428        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                // Handle any comments in between the value and the punctuation
444                // If they are present, then move them to after the punctuation
445                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
470/// Formats a Punctuated sequence, depending on its layout. If the sequence contains comments, we will format
471/// across multiple lines
472pub 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    // TODO: we do not check the leading comments of the punctuated list for determining multiline
490    // Maybe we should do later?
491    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
500/// Formats a Punctuated sequence contained within parentheses onto multiple lines
501/// In particular, it handles the indentation of the sequence within the parentheses, and comments
502pub fn format_contained_punctuated_multiline<T, F1>(
503    ctx: &Context,
504    parentheses: &ContainedSpan,
505    arguments: &Punctuated<T>,
506    argument_formatter: F1, // Function to format the argument
507    shape: Shape,
508) -> (ContainedSpan, Punctuated<T>)
509where
510    T: UpdateLeadingTrivia + GetTrailingTrivia + UpdateTrailingTrivia,
511    F1: Fn(&Context, &T, Shape) -> T,
512{
513    // Format start and end brace properly with correct trivia
514    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(); // Argument is on a new line, so reset the shape
530
531        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        // Any singleline comments must be moved to after the punctuation
537        // We should keep multiline comments in the same location
538        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                // Continue adding a comma and a new line for multiline function args
549                // Also add any trailing comments we have taken from the expression
550                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                        // Prepend a single space beforehand
558                        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            // TODO/HACK: we create a phantom comma which is just actually a new line
576            // We need to do this because in function declarations, we format parameters but if they have a type
577            // specifier we don't have access to put it after the type specifier
578            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
604/// Formats a special TokenReference which is a symbol
605/// Used to preserve the comments around the symbol
606pub fn format_symbol(
607    ctx: &Context,
608    current_symbol: &TokenReference,
609    wanted_symbol: &TokenReference,
610    shape: Shape,
611) -> TokenReference {
612    // Preserve comments in leading/trailing trivia
613    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    // Add on any whitespace created in the new symbol
627    // The wanted leading trivia should be added to the end of formatted_leading_trivia
628    // whilst the wanted trailing trivia should be added to the start of formatted_trailing_trivia
629    // so that the token is "wrapped" around
630    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
648/// Formats a token present at the end of an indented block, such as the `end` token or closing brace in a multiline table.
649/// This is required due to leading comments bound to the last token - they need to have one level higher indentation
650pub fn format_end_token(
651    ctx: &Context,
652    current_token: &TokenReference,
653    token_type: EndTokenType,
654    shape: Shape,
655) -> TokenReference {
656    // Indent any comments leading a token, as these comments are technically part of the function body block
657    let formatted_leading_trivia: Vec<Token> = load_token_trivia(
658        ctx,
659        current_token.leading_trivia().collect(),
660        FormatTokenType::LeadingTrivia,
661        // The indent level we are currently at is one less (as we are at the block closing token, not the indented block).
662        // The comment is present inside the indented block
663        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    // Special case for block end tokens:
677    // We will reverse the leading trivia, and keep removing any newlines we find, until we find something else, then we stop.
678    // This is to remove unnecessary newlines at the end of the block.
679    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; // Stop removing newlines once we have seen some sort of comment
701            }
702        }
703    }
704
705    // Need to reverse the vector since we reversed the iterator
706    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
715/// Continues mutating a Vec of Tokens until there is no more trailing whitespace present
716fn 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), // Keep popping until no more whitespace
720            _ => trivia.push(t), // Its not whitespace, so add it back and stop popping
721        }
722    }
723}
724
725/// Format the EOF token.
726/// This is done by removing any leading whitespace, whilst preserving leading comments.
727/// An EOF token has no trailing trivia
728pub 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    // Need to preserve any comments in leading_trivia if present
734    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        // Remove all the whitespace, and return an empty EOF
746        TokenReference::new(Vec::new(), Token::new(TokenType::Eof), Vec::new())
747    } else {
748        // We have some comments in here, so we need to remove any trailing whitespace then add a single new line
749        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}