Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Utilities for formatting macro expanded nodes until we get a proper formatter.
use syntax::{
    ast::make,
    ted::{self, Position},
    NodeOrToken,
    SyntaxKind::{self, *},
    SyntaxNode, SyntaxToken, WalkEvent, T,
};

// FIXME: It would also be cool to share logic here and in the mbe tests,
// which are pretty unreadable at the moment.
/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
    let mut indent = 0;
    let mut last: Option<SyntaxKind> = None;
    let mut mods = Vec::new();
    let syn = syn.clone_subtree().clone_for_update();

    let before = Position::before;
    let after = Position::after;

    let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
        (pos(token.clone()), make::tokens::whitespace(&" ".repeat(2 * indent)))
    };
    let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
        (pos(token.clone()), make::tokens::single_space())
    };
    let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
        (pos(token.clone()), make::tokens::single_newline())
    };

    for event in syn.preorder_with_tokens() {
        let token = match event {
            WalkEvent::Enter(NodeOrToken::Token(token)) => token,
            WalkEvent::Leave(NodeOrToken::Node(node))
                if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) =>
            {
                if indent > 0 {
                    mods.push((
                        Position::after(node.clone()),
                        make::tokens::whitespace(&" ".repeat(2 * indent)),
                    ));
                }
                if node.parent().is_some() {
                    mods.push((Position::after(node), make::tokens::single_newline()));
                }
                continue;
            }
            _ => continue,
        };
        let tok = &token;

        let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
            tok.next_token().map(|it| f(it.kind())).unwrap_or(default)
        };
        let is_last =
            |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };

        match tok.kind() {
            k if is_text(k) && is_next(|it| !it.is_punct() || it == UNDERSCORE, false) => {
                mods.push(do_ws(after, tok));
            }
            L_CURLY if is_next(|it| it != R_CURLY, true) => {
                indent += 1;
                if is_last(is_text, false) {
                    mods.push(do_ws(before, tok));
                }

                if indent > 0 {
                    mods.push(do_indent(after, tok, indent));
                }
                mods.push(do_nl(after, tok));
            }
            R_CURLY if is_last(|it| it != L_CURLY, true) => {
                indent = indent.saturating_sub(1);

                if indent > 0 {
                    mods.push(do_indent(before, tok, indent));
                }
                mods.push(do_nl(before, tok));
            }
            R_CURLY => {
                if indent > 0 {
                    mods.push(do_indent(after, tok, indent));
                }
                mods.push(do_nl(after, tok));
            }
            LIFETIME_IDENT if is_next(is_text, true) => {
                mods.push(do_ws(after, tok));
            }
            MUT_KW if is_next(|it| it == SELF_KW, false) => {
                mods.push(do_ws(after, tok));
            }
            AS_KW | DYN_KW | IMPL_KW | CONST_KW => {
                mods.push(do_ws(after, tok));
            }
            T![;] => {
                if indent > 0 {
                    mods.push(do_indent(after, tok, indent));
                }
                mods.push(do_nl(after, tok));
            }
            T![->] | T![=] | T![=>] => {
                mods.push(do_ws(before, tok));
                mods.push(do_ws(after, tok));
            }
            _ => (),
        }

        last = Some(tok.kind());
    }

    for (pos, insert) in mods {
        ted::insert(pos, insert);
    }

    if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
        ted::remove(it);
    }

    syn
}

fn is_text(k: SyntaxKind) -> bool {
    k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE
}