1 //! Utilities for formatting macro expanded nodes until we get a proper formatter.
2 use syntax::{
3     ast::make,
4     ted::{self, Position},
5     NodeOrToken,
6     SyntaxKind::{self, *},
7     SyntaxNode, SyntaxToken, WalkEvent, T,
8 };
9 
10 // FIXME: It would also be cool to share logic here and in the mbe tests,
11 // which are pretty unreadable at the moment.
12 /// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
insert_ws_into(syn: SyntaxNode) -> SyntaxNode13 pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
14     let mut indent = 0;
15     let mut last: Option<SyntaxKind> = None;
16     let mut mods = Vec::new();
17     let syn = syn.clone_subtree().clone_for_update();
18 
19     let before = Position::before;
20     let after = Position::after;
21 
22     let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
23         (pos(token.clone()), make::tokens::whitespace(&" ".repeat(2 * indent)))
24     };
25     let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
26         (pos(token.clone()), make::tokens::single_space())
27     };
28     let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
29         (pos(token.clone()), make::tokens::single_newline())
30     };
31 
32     for event in syn.preorder_with_tokens() {
33         let token = match event {
34             WalkEvent::Enter(NodeOrToken::Token(token)) => token,
35             WalkEvent::Leave(NodeOrToken::Node(node))
36                 if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) =>
37             {
38                 if indent > 0 {
39                     mods.push((
40                         Position::after(node.clone()),
41                         make::tokens::whitespace(&" ".repeat(2 * indent)),
42                     ));
43                 }
44                 if node.parent().is_some() {
45                     mods.push((Position::after(node), make::tokens::single_newline()));
46                 }
47                 continue;
48             }
49             _ => continue,
50         };
51         let tok = &token;
52 
53         let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
54             tok.next_token().map(|it| f(it.kind())).unwrap_or(default)
55         };
56         let is_last =
57             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
58 
59         match tok.kind() {
60             k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
61                 mods.push(do_ws(after, tok));
62             }
63             L_CURLY if is_next(|it| it != R_CURLY, true) => {
64                 indent += 1;
65                 if is_last(is_text, false) {
66                     mods.push(do_ws(before, tok));
67                 }
68 
69                 if indent > 0 {
70                     mods.push(do_indent(after, tok, indent));
71                 }
72                 mods.push(do_nl(after, &tok));
73             }
74             R_CURLY if is_last(|it| it != L_CURLY, true) => {
75                 indent = indent.saturating_sub(1);
76 
77                 if indent > 0 {
78                     mods.push(do_indent(before, tok, indent));
79                 }
80                 mods.push(do_nl(before, tok));
81             }
82             R_CURLY => {
83                 if indent > 0 {
84                     mods.push(do_indent(after, tok, indent));
85                 }
86                 mods.push(do_nl(after, tok));
87             }
88             LIFETIME_IDENT if is_next(|it| is_text(it), true) => {
89                 mods.push(do_ws(after, tok));
90             }
91             AS_KW => {
92                 mods.push(do_ws(after, tok));
93             }
94             T![;] => {
95                 if indent > 0 {
96                     mods.push(do_indent(after, tok, indent));
97                 }
98                 mods.push(do_nl(after, tok));
99             }
100             T![->] | T![=] | T![=>] => {
101                 mods.push(do_ws(before, tok));
102                 mods.push(do_ws(after, tok));
103             }
104             _ => (),
105         }
106 
107         last = Some(tok.kind());
108     }
109 
110     for (pos, insert) in mods {
111         ted::insert(pos, insert);
112     }
113 
114     syn
115 }
116 
is_text(k: SyntaxKind) -> bool117 fn is_text(k: SyntaxKind) -> bool {
118     k.is_keyword() || k.is_literal() || k == IDENT
119 }
120