stylua_lib/formatters/
general.rs

1#[cfg(feature = "cfxlua")]
2use crate::LuaVersion;
3use crate::{
4    context::{
5        create_indent_trivia, create_newline_trivia, line_ending_character, Context, FormatNode,
6    },
7    formatters::{
8        trivia::{FormatTriviaType, UpdateLeadingTrivia, UpdateTrailingTrivia, UpdateTrivia},
9        trivia_util::{
10            self, punctuated_inline_comments, CommentSearch, GetLeadingTrivia, GetTrailingTrivia,
11            HasInlineComments,
12        },
13    },
14    shape::Shape,
15    QuoteStyle,
16};
17use full_moon::node::Node;
18use full_moon::tokenizer::{StringLiteralQuoteType, Token, TokenKind, TokenReference, TokenType};
19use full_moon::{
20    ast::{
21        punctuated::{Pair, Punctuated},
22        span::ContainedSpan,
23    },
24    ShortString,
25};
26
27#[derive(Debug, Clone, Copy)]
28pub enum FormatTokenType {
29    Token,
30    LeadingTrivia,
31    TrailingTrivia,
32}
33
34/// The type of end token being used to format
35#[derive(Debug)]
36pub enum EndTokenType {
37    /// Indent the leading comments of the token.
38    /// This typically occurs when the token is used to close an indented block scope
39    IndentComments,
40    /// Keep the comments at the same level as the end token
41    InlineComments,
42}
43
44#[macro_export]
45macro_rules! fmt_symbol {
46    ($ctx:expr, $token:expr, $x:expr, $shape:expr) => {
47        $crate::formatters::general::format_symbol(
48            $ctx,
49            $token,
50            &TokenReference::symbol_specific_lua_version($x, $ctx.config().syntax.into()).unwrap(),
51            $shape,
52        )
53    };
54}
55
56fn get_quote_to_use(ctx: &Context, literal: &str) -> StringLiteralQuoteType {
57    match ctx.config().quote_style {
58        QuoteStyle::ForceDouble => StringLiteralQuoteType::Double,
59        QuoteStyle::ForceSingle => StringLiteralQuoteType::Single,
60        _ => {
61            let preferred = match ctx.config().quote_style {
62                QuoteStyle::AutoPreferDouble => StringLiteralQuoteType::Double,
63                QuoteStyle::AutoPreferSingle => StringLiteralQuoteType::Single,
64                _ => unreachable!("have other quote styles we haven't looked into yet"),
65            };
66
67            // Check to see if there is a quote within it
68            if literal.contains('\'') || literal.contains('"') {
69                let num_single_quotes = literal.matches('\'').count();
70                let num_double_quotes = literal.matches('"').count();
71
72                match num_single_quotes.cmp(&num_double_quotes) {
73                    std::cmp::Ordering::Equal => preferred,
74                    std::cmp::Ordering::Greater => StringLiteralQuoteType::Double,
75                    std::cmp::Ordering::Less => StringLiteralQuoteType::Single,
76                }
77            } else {
78                preferred
79            }
80        }
81    }
82}
83
84fn format_single_line_comment_string(comment: &str) -> &str {
85    // Trim any trailing whitespace
86    comment.trim_end()
87}
88
89pub fn trivia_to_vec(
90    (token, leading, trailing): (Token, Option<Vec<Token>>, Option<Vec<Token>>),
91) -> Vec<Token> {
92    let mut output = leading.unwrap_or_default();
93    output.push(token);
94    output.append(&mut trailing.unwrap_or_default());
95
96    output
97}
98
99fn format_string_literal(
100    ctx: &Context,
101    literal: &ShortString,
102    multi_line_depth: &usize,
103    quote_type: &StringLiteralQuoteType,
104) -> TokenType {
105    // CfxLua: if we have a backtick string, don't mess with it
106    #[cfg(feature = "cfxlua")]
107    if ctx.config().syntax == LuaVersion::CfxLua
108        && matches!(quote_type, StringLiteralQuoteType::Backtick)
109    {
110        return TokenType::StringLiteral {
111            literal: literal.clone(),
112            multi_line_depth: *multi_line_depth,
113            quote_type: StringLiteralQuoteType::Backtick,
114        };
115    }
116
117    // If we have a brackets string, don't mess with it
118    if let StringLiteralQuoteType::Brackets = quote_type {
119        // Convert the string to the correct line endings, by first normalising to LF
120        // then converting LF to output
121        let literal = literal
122            .replace("\r\n", "\n")
123            .replace('\n', &line_ending_character(ctx.config().line_endings));
124
125        TokenType::StringLiteral {
126            literal: literal.into(),
127            multi_line_depth: *multi_line_depth,
128            quote_type: StringLiteralQuoteType::Brackets,
129        }
130    } else {
131        // Match all escapes within the string
132        // Based off https://github.com/prettier/prettier/blob/181a325c1c07f1a4f3738665b7b28288dfb960bc/src/common/util.js#L439
133        lazy_static::lazy_static! {
134            static ref RE: regex::Regex = regex::Regex::new(r#"\\?(["'])|\\([\S\s])"#).unwrap();
135            static ref UNNECESSARY_ESCAPES: regex::Regex = regex::Regex::new(r#"^[^\n\r"'0-9\\abfnrtuvxz]$"#).unwrap();
136        }
137        let quote_to_use = get_quote_to_use(ctx, literal);
138        let literal = RE
139            .replace_all(literal, |caps: &regex::Captures| {
140                let quote = caps.get(1);
141                let escaped = caps.get(2);
142
143                match quote {
144                    Some(quote) => {
145                        // We have a quote, find what type it is, and see if we need to escape it
146                        // then return the output string
147                        match quote.as_str() {
148                            "'" => {
149                                // Check whether to escape the quote
150                                if let StringLiteralQuoteType::Single = quote_to_use {
151                                    String::from("\\'")
152                                } else {
153                                    String::from("'")
154                                }
155                            }
156                            "\"" => {
157                                // Check whether to escape the quote
158                                if let StringLiteralQuoteType::Double = quote_to_use {
159                                    String::from("\\\"")
160                                } else {
161                                    String::from("\"")
162                                }
163                            }
164                            other => unreachable!("unknown quote type {:?}", other),
165                        }
166                    }
167                    None => {
168                        // We have a normal escape
169                        // Test to see if it is necessary, and if not, then unescape it
170                        let text = escaped
171                            .expect("have a match which was neither an escape or a quote")
172                            .as_str();
173                        if UNNECESSARY_ESCAPES.is_match(text) {
174                            text.to_owned()
175                        } else {
176                            format!("\\{}", text.to_owned())
177                        }
178                    }
179                }
180            })
181            .into();
182
183        TokenType::StringLiteral {
184            literal,
185            multi_line_depth: *multi_line_depth,
186            quote_type: quote_to_use,
187        }
188    }
189}
190
191/// Formats a Token Node
192/// Also returns any extra leading or trailing trivia to add for the Token node
193/// This should only ever be called from format_token_reference
194pub fn format_token(
195    ctx: &Context,
196    token: &Token,
197    format_type: FormatTokenType,
198    shape: Shape,
199) -> (Token, Option<Vec<Token>>, Option<Vec<Token>>) {
200    let mut leading_trivia: Option<Vec<Token>> = None;
201    let mut trailing_trivia: Option<Vec<Token>> = None;
202
203    let token_type = match token.token_type() {
204        TokenType::Number { text } => {
205            let text = if text.starts_with('.') {
206                String::from("0") + text.as_str()
207            } else if text.starts_with("-.") {
208                String::from("-0") + text.get(1..).expect("unknown number literal")
209            } else {
210                text.to_string()
211            }
212            .into();
213
214            TokenType::Number { text }
215        }
216        TokenType::StringLiteral {
217            literal,
218            multi_line_depth,
219            quote_type,
220        } => format_string_literal(ctx, literal, multi_line_depth, quote_type),
221        TokenType::Shebang { line } => {
222            let line = format_single_line_comment_string(line).into();
223
224            // Shebang must always be leading trivia, as it is start of file. Terminate it with a newline
225            debug_assert!(matches!(format_type, FormatTokenType::LeadingTrivia));
226            trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
227
228            TokenType::Shebang { line }
229        }
230        TokenType::SingleLineComment { comment } => {
231            let comment = format_single_line_comment_string(comment).into();
232
233            match format_type {
234                FormatTokenType::LeadingTrivia => {
235                    leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
236                    trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
237                }
238                FormatTokenType::TrailingTrivia => {
239                    // Add a space before the comment
240                    leading_trivia = Some(vec![Token::new(TokenType::spaces(1))]);
241                }
242                _ => (),
243            }
244
245            TokenType::SingleLineComment { comment }
246        }
247        TokenType::MultiLineComment { blocks, comment } => {
248            if let FormatTokenType::LeadingTrivia = format_type {
249                leading_trivia = Some(vec![create_indent_trivia(ctx, shape)]);
250                // Add a new line once the comment is completed
251                trailing_trivia = Some(vec![create_newline_trivia(ctx)]);
252            }
253
254            // Convert the comment to the correct line endings, by first normalising to LF
255            // then converting LF to output
256            let comment = comment
257                .replace("\r\n", "\n")
258                .replace('\n', &line_ending_character(ctx.config().line_endings));
259
260            TokenType::MultiLineComment {
261                blocks: *blocks,
262                comment: comment.into(),
263            }
264        }
265        TokenType::Whitespace { characters } => TokenType::Whitespace {
266            characters: characters.to_owned(),
267        }, // TODO
268        #[cfg(feature = "luau")]
269        TokenType::InterpolatedString { literal, kind } => TokenType::InterpolatedString {
270            literal: literal.to_owned(),
271            kind: kind.to_owned(),
272        },
273        _ => token.token_type().to_owned(),
274    };
275
276    (Token::new(token_type), leading_trivia, trailing_trivia)
277}
278
279/// Wraps around the format_token function to create a complete list of trivia to add to a node.
280/// Handles any leading/trailing trivia provided by format_token, and appends it accordingly in relation to the formatted token.
281/// Mainly useful for comments
282/// Additional indent level will indent any trivia by the further level - useful for comments on the `end` token
283fn load_token_trivia(
284    ctx: &Context,
285    current_trivia: Vec<&Token>,
286    format_token_type: FormatTokenType,
287    shape: Shape,
288) -> Vec<Token> {
289    let mut token_trivia = Vec::new();
290
291    let mut newline_count_in_succession = 0;
292    let mut trivia_iter = current_trivia.iter().peekable();
293
294    while let Some(trivia) = trivia_iter.next() {
295        match trivia.token_type() {
296            TokenType::Whitespace { characters } => {
297                // Handle cases where the user has left a newline gap in between e.g. two statements
298                // If we are formatting trailing trivia, this can be ignored, as all trailing newlines will have already
299                // been handled by the formatter.
300                // If we are formatting leading trivia, we will allow a single newline to be kept in succession, if we
301                // find one.
302                match format_token_type {
303                    FormatTokenType::LeadingTrivia => {
304                        if characters.contains('\n') {
305                            newline_count_in_succession += 1;
306                            if newline_count_in_succession == 1 {
307                                // We have a case where we will allow a single newline to be kept
308                                token_trivia.push(create_newline_trivia(ctx));
309                            }
310                        }
311                    }
312                    FormatTokenType::TrailingTrivia => {
313                        // If the next trivia is a MultiLineComment, and this whitespace is just spacing, then we
314                        // will preserve a single space
315                        if let Some(next_trivia) = trivia_iter.peek() {
316                            if let TokenType::MultiLineComment { .. } = next_trivia.token_type() {
317                                if !characters.contains('\n') {
318                                    token_trivia.push(Token::new(TokenType::spaces(1)))
319                                }
320                            }
321                        }
322                    }
323                    _ => (),
324                }
325
326                // Move to next trivia
327                continue;
328            }
329            TokenType::Shebang { .. }
330            | TokenType::SingleLineComment { .. }
331            | TokenType::MultiLineComment { .. } => {
332                // If we have a comment, when `format_token` is called, it will put a newline at the end
333                // If this happens, we want to skip the next iteration if its a newline, as that has already been covered here
334                if let FormatTokenType::LeadingTrivia = format_token_type {
335                    if let Some(next_trivia) = trivia_iter.peek() {
336                        if let TokenType::Whitespace { characters } = next_trivia.token_type() {
337                            if characters.contains('\n') {
338                                // Consume iterator once to skip the next iteration
339                                trivia_iter.next();
340                            }
341                        }
342                    }
343                }
344                // We will reset the counter as well, because the newline above is only to terminate the comment
345                newline_count_in_succession = 0;
346            }
347            _ => {
348                // Reset new line counter, as we only want two new lines in a row
349                newline_count_in_succession = 0;
350            }
351        }
352
353        let (token, leading_trivia, trailing_trivia) =
354            format_token(ctx, trivia.to_owned(), format_token_type, shape);
355        if let Some(mut trivia) = leading_trivia {
356            token_trivia.append(&mut trivia);
357        }
358
359        token_trivia.push(token);
360
361        if let Some(mut trivia) = trailing_trivia {
362            token_trivia.append(&mut trivia)
363        }
364    }
365
366    token_trivia
367}
368
369pub fn format_token_reference(
370    ctx: &Context,
371    token_reference: &TokenReference,
372    shape: Shape,
373) -> TokenReference {
374    // Preserve comments in leading/trailing trivia
375    let formatted_leading_trivia: Vec<Token> = load_token_trivia(
376        ctx,
377        token_reference.leading_trivia().collect(),
378        FormatTokenType::LeadingTrivia,
379        shape,
380    );
381    let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
382        ctx,
383        token_reference.trailing_trivia().collect(),
384        FormatTokenType::TrailingTrivia,
385        shape,
386    );
387
388    let (token, _leading_trivia, _trailing_trivia) =
389        format_token(ctx, token_reference.token(), FormatTokenType::Token, shape);
390
391    TokenReference::new(formatted_leading_trivia, token, formatted_trailing_trivia)
392}
393
394/// Formats a Punctuated sequence with correct punctuated values.
395/// This function assumes that there are no comments present which would lead to a syntax error if the list was collapsed.
396/// If not sure about comments, [`try_format_punctuated`] should be used instead.
397pub fn format_punctuated<T, F>(
398    ctx: &Context,
399    old: &Punctuated<T>,
400    shape: Shape,
401    value_formatter: F,
402) -> Punctuated<T>
403where
404    T: std::fmt::Display,
405    F: Fn(&Context, &T, Shape) -> T,
406{
407    let mut list: Punctuated<T> = Punctuated::new();
408    let mut shape = shape;
409
410    for pair in old.pairs() {
411        match pair {
412            Pair::Punctuated(value, punctuation) => {
413                let value = value_formatter(ctx, value, shape);
414                let punctuation = fmt_symbol!(ctx, punctuation, ",", shape).update_trailing_trivia(
415                    FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))]),
416                );
417                shape = shape.take_last_line(&value) + 2; // 2 = ", "
418
419                list.push(Pair::new(value, Some(punctuation)));
420            }
421            Pair::End(value) => {
422                let value = value_formatter(ctx, value, shape);
423                list.push(Pair::new(value, None));
424            }
425        }
426    }
427
428    list
429}
430
431// Formats a Punctuated sequence across multiple lines. Also indents each item by hang_level
432pub fn format_punctuated_multiline<T, F>(
433    ctx: &Context,
434    old: &Punctuated<T>,
435    shape: Shape,
436    value_formatter: F,
437    hang_level: Option<usize>,
438) -> Punctuated<T>
439where
440    T: Node + GetLeadingTrivia + UpdateLeadingTrivia + GetTrailingTrivia + UpdateTrailingTrivia,
441    F: Fn(&Context, &T, Shape) -> T,
442{
443    let mut formatted: Punctuated<T> = Punctuated::new();
444
445    // Include hang level if required
446    let hanging_shape = match hang_level {
447        Some(hang_level) => shape.with_indent(shape.indent().add_indent_level(hang_level)),
448        None => shape,
449    };
450
451    for (idx, pair) in old.pairs().enumerate() {
452        // Indent the pair (unless its the first item)
453        let shape = if idx == 0 {
454            shape
455        } else {
456            hanging_shape.reset()
457        };
458
459        match pair {
460            Pair::Punctuated(value, punctuation) => {
461                let value = value_formatter(ctx, value, shape);
462                let value = if idx == 0 {
463                    value
464                } else {
465                    trivia_util::prepend_newline_indent(ctx, &value, hanging_shape)
466                };
467
468                // Handle any comments in between the value and the punctuation
469                // If they are present, then move them to after the punctuation
470                let mut trailing_comments = value.trailing_comments();
471                let value = value.update_trailing_trivia(FormatTriviaType::Replace(vec![]));
472
473                let punctuation = fmt_symbol!(ctx, punctuation, ",", shape);
474                trailing_comments.append(&mut punctuation.trailing_trivia().cloned().collect());
475                let punctuation = punctuation
476                    .update_trailing_trivia(FormatTriviaType::Replace(trailing_comments));
477
478                formatted.push(Pair::new(value, Some(punctuation)));
479            }
480            Pair::End(value) => {
481                let value = value_formatter(ctx, value, shape);
482                let value = if idx == 0 {
483                    value
484                } else {
485                    trivia_util::prepend_newline_indent(ctx, &value, hanging_shape)
486                };
487                formatted.push(Pair::new(value, None));
488            }
489        }
490    }
491
492    formatted
493}
494
495/// Formats a Punctuated sequence, depending on its layout. If the sequence contains comments, we will format
496/// across multiple lines
497pub fn try_format_punctuated<T, F>(
498    ctx: &Context,
499    old: &Punctuated<T>,
500    shape: Shape,
501    value_formatter: F,
502    hang_level: Option<usize>,
503) -> Punctuated<T>
504where
505    T: Node
506        + GetLeadingTrivia
507        + UpdateLeadingTrivia
508        + GetTrailingTrivia
509        + UpdateTrailingTrivia
510        + HasInlineComments
511        + std::fmt::Display,
512    F: Fn(&Context, &T, Shape) -> T,
513{
514    // TODO: we do not check the leading comments of the punctuated list for determining multiline
515    // Maybe we should do later?
516    let format_multiline = punctuated_inline_comments(old, false);
517
518    if format_multiline {
519        format_punctuated_multiline(ctx, old, shape, value_formatter, hang_level)
520    } else {
521        format_punctuated(ctx, old, shape, value_formatter)
522    }
523}
524
525/// Formats a Punctuated sequence contained within parentheses onto multiple lines
526/// In particular, it handles the indentation of the sequence within the parentheses, and comments
527pub fn format_contained_punctuated_multiline<T, F1>(
528    ctx: &Context,
529    parentheses: &ContainedSpan,
530    arguments: &Punctuated<T>,
531    argument_formatter: F1, // Function to format the argument
532    shape: Shape,
533) -> (ContainedSpan, Punctuated<T>)
534where
535    T: UpdateLeadingTrivia + GetTrailingTrivia + UpdateTrailingTrivia,
536    F1: Fn(&Context, &T, Shape) -> T,
537{
538    // Format start and end brace properly with correct trivia
539    let (start_parens, end_parens) = parentheses.tokens();
540    let start_parens = format_token_reference(ctx, start_parens, shape)
541        .update_trailing_trivia(FormatTriviaType::Append(vec![create_newline_trivia(ctx)]));
542
543    let end_parens = format_end_token(ctx, end_parens, EndTokenType::IndentComments, shape)
544        .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
545            ctx, shape,
546        )]));
547
548    let parentheses = ContainedSpan::new(start_parens, end_parens);
549
550    let mut formatted_arguments = Punctuated::new();
551    let shape = shape.increment_additional_indent();
552
553    for argument in arguments.pairs() {
554        let shape = shape.reset(); // Argument is on a new line, so reset the shape
555
556        let formatted_argument = argument_formatter(ctx, argument.value(), shape)
557            .update_leading_trivia(FormatTriviaType::Append(vec![create_indent_trivia(
558                ctx, shape,
559            )]));
560
561        // Any singleline comments must be moved to after the punctuation
562        // We should keep multiline comments in the same location
563        let multiline_comments =
564            formatted_argument.trailing_comments_search(CommentSearch::Multiline);
565        let singleline_comments =
566            formatted_argument.trailing_comments_search(CommentSearch::Single);
567
568        let formatted_argument = formatted_argument
569            .update_trailing_trivia(FormatTriviaType::Replace(multiline_comments));
570
571        let punctuation = match argument.punctuation() {
572            Some(punctuation) => {
573                // Continue adding a comma and a new line for multiline function args
574                // Also add any trailing comments we have taken from the expression
575                let symbol = fmt_symbol!(ctx, punctuation, ",", shape);
576
577                let mut trailing_trivia: Vec<_> = symbol
578                    .leading_trivia()
579                    .filter(|trivia| trivia_util::trivia_is_comment(trivia))
580                    .cloned()
581                    .flat_map(|x| {
582                        // Prepend a single space beforehand
583                        vec![
584                            create_newline_trivia(ctx),
585                            create_indent_trivia(ctx, shape),
586                            x,
587                        ]
588                    })
589                    .chain(singleline_comments)
590                    .collect();
591                trailing_trivia.push(create_newline_trivia(ctx));
592
593                let symbol = symbol.update_trivia(
594                    FormatTriviaType::Replace(vec![]),
595                    FormatTriviaType::Append(trailing_trivia),
596                );
597
598                Some(symbol)
599            }
600            // TODO/HACK: we create a phantom comma which is just actually a new line
601            // We need to do this because in function declarations, we format parameters but if they have a type
602            // specifier we don't have access to put it after the type specifier
603            None => Some(TokenReference::new(
604                singleline_comments,
605                create_newline_trivia(ctx),
606                vec![],
607            )),
608        };
609
610        formatted_arguments.push(Pair::new(formatted_argument, punctuation))
611    }
612
613    (parentheses, formatted_arguments)
614}
615
616pub fn format_contained_span(
617    ctx: &Context,
618    contained_span: &ContainedSpan,
619    shape: Shape,
620) -> ContainedSpan {
621    let (start_token, end_token) = contained_span.tokens();
622
623    ContainedSpan::new(
624        format_token_reference(ctx, start_token, shape),
625        format_token_reference(ctx, end_token, shape),
626    )
627}
628
629/// Formats a special TokenReference which is a symbol
630/// Used to preserve the comments around the symbol
631pub fn format_symbol(
632    ctx: &Context,
633    current_symbol: &TokenReference,
634    wanted_symbol: &TokenReference,
635    shape: Shape,
636) -> TokenReference {
637    // Preserve comments in leading/trailing trivia
638    let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
639        ctx,
640        current_symbol.leading_trivia().collect(),
641        FormatTokenType::LeadingTrivia,
642        shape,
643    );
644    let mut formatted_trailing_trivia: Vec<Token> = load_token_trivia(
645        ctx,
646        current_symbol.trailing_trivia().collect(),
647        FormatTokenType::TrailingTrivia,
648        shape,
649    );
650
651    // Add on any whitespace created in the new symbol
652    // The wanted leading trivia should be added to the end of formatted_leading_trivia
653    // whilst the wanted trailing trivia should be added to the start of formatted_trailing_trivia
654    // so that the token is "wrapped" around
655    let mut wanted_leading_trivia: Vec<Token> = wanted_symbol
656        .leading_trivia()
657        .map(|x| x.to_owned())
658        .collect();
659    let mut wanted_trailing_trivia: Vec<Token> = wanted_symbol
660        .trailing_trivia()
661        .map(|x| x.to_owned())
662        .collect();
663    wanted_trailing_trivia.append(&mut formatted_trailing_trivia);
664    formatted_leading_trivia.append(&mut wanted_leading_trivia);
665
666    TokenReference::new(
667        formatted_leading_trivia,
668        wanted_symbol.token().to_owned(),
669        wanted_trailing_trivia,
670    )
671}
672
673/// Formats a token present at the end of an indented block, such as the `end` token or closing brace in a multiline table.
674/// This is required due to leading comments bound to the last token - they need to have one level higher indentation
675pub fn format_end_token(
676    ctx: &Context,
677    current_token: &TokenReference,
678    token_type: EndTokenType,
679    shape: Shape,
680) -> TokenReference {
681    // Indent any comments leading a token, as these comments are technically part of the function body block
682    let formatted_leading_trivia: Vec<Token> = load_token_trivia(
683        ctx,
684        current_token.leading_trivia().collect(),
685        FormatTokenType::LeadingTrivia,
686        // The indent level we are currently at is one less (as we are at the block closing token, not the indented block).
687        // The comment is present inside the indented block
688        if let EndTokenType::IndentComments = token_type {
689            shape.increment_additional_indent()
690        } else {
691            shape
692        },
693    );
694    let formatted_trailing_trivia: Vec<Token> = load_token_trivia(
695        ctx,
696        current_token.trailing_trivia().collect(),
697        FormatTokenType::TrailingTrivia,
698        shape,
699    );
700
701    // Special case for block end tokens:
702    // We will reverse the leading trivia, and keep removing any newlines we find, until we find something else, then we stop.
703    // This is to remove unnecessary newlines at the end of the block.
704    let mut iter = formatted_leading_trivia.iter().rev().peekable();
705
706    let mut formatted_leading_trivia = Vec::new();
707    let mut stop_removal = false;
708    while let Some(x) = iter.next() {
709        match x.token_type() {
710            TokenType::Whitespace { ref characters } => {
711                if !stop_removal
712                    && characters.contains('\n')
713                    && !matches!(
714                        iter.peek().map(|x| x.token_kind()),
715                        Some(TokenKind::SingleLineComment) | Some(TokenKind::MultiLineComment)
716                    )
717                {
718                    continue;
719                } else {
720                    formatted_leading_trivia.push(x.to_owned());
721                }
722            }
723            _ => {
724                formatted_leading_trivia.push(x.to_owned());
725                stop_removal = true; // Stop removing newlines once we have seen some sort of comment
726            }
727        }
728    }
729
730    // Need to reverse the vector since we reversed the iterator
731    formatted_leading_trivia.reverse();
732
733    TokenReference::new(
734        formatted_leading_trivia,
735        Token::new(current_token.token_type().to_owned()),
736        formatted_trailing_trivia,
737    )
738}
739
740/// Continues mutating a Vec of Tokens until there is no more trailing whitespace present
741fn pop_until_no_whitespace(trivia: &mut Vec<Token>) {
742    if let Some(t) = trivia.pop() {
743        match t.token_kind() {
744            TokenKind::Whitespace => pop_until_no_whitespace(trivia), // Keep popping until no more whitespace
745            _ => trivia.push(t), // Its not whitespace, so add it back and stop popping
746        }
747    }
748}
749
750/// Format the EOF token.
751/// This is done by removing any leading whitespace, whilst preserving leading comments.
752/// An EOF token has no trailing trivia
753pub fn format_eof(ctx: &Context, eof: &TokenReference, shape: Shape) -> TokenReference {
754    if ctx.should_format_node(eof) != FormatNode::Normal {
755        return eof.to_owned();
756    }
757
758    // Need to preserve any comments in leading_trivia if present
759    let mut formatted_leading_trivia: Vec<Token> = load_token_trivia(
760        ctx,
761        eof.leading_trivia().collect(),
762        FormatTokenType::LeadingTrivia,
763        shape,
764    );
765
766    let only_whitespace = formatted_leading_trivia
767        .iter()
768        .all(|x| x.token_kind() == TokenKind::Whitespace);
769    if only_whitespace {
770        // Remove all the whitespace, and return an empty EOF
771        TokenReference::new(Vec::new(), Token::new(TokenType::Eof), Vec::new())
772    } else {
773        // We have some comments in here, so we need to remove any trailing whitespace then add a single new line
774        pop_until_no_whitespace(&mut formatted_leading_trivia);
775        formatted_leading_trivia.push(create_newline_trivia(ctx));
776
777        TokenReference::new(
778            formatted_leading_trivia,
779            Token::new(TokenType::Eof),
780            Vec::new(),
781        )
782    }
783}