]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/expand_macro.rs
rename mock_analysis -> fixture
[rust.git] / crates / ide / src / expand_macro.rs
1 use hir::Semantics;
2 use ide_db::RootDatabase;
3 use syntax::{
4     algo::{find_node_at_offset, SyntaxRewriter},
5     ast, AstNode, NodeOrToken, SyntaxKind,
6     SyntaxKind::*,
7     SyntaxNode, WalkEvent, T,
8 };
9
10 use crate::FilePosition;
11
12 pub struct ExpandedMacro {
13     pub name: String,
14     pub expansion: String,
15 }
16
17 // Feature: Expand Macro Recursively
18 //
19 // Shows the full macro expansion of the macro at current cursor.
20 //
21 // |===
22 // | Editor  | Action Name
23 //
24 // | VS Code | **Rust Analyzer: Expand macro recursively**
25 // |===
26 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
27     let sema = Semantics::new(db);
28     let file = sema.parse(position.file_id);
29     let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
30     let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
31
32     let expanded = expand_macro_recur(&sema, &mac)?;
33
34     // FIXME:
35     // macro expansion may lose all white space information
36     // But we hope someday we can use ra_fmt for that
37     let expansion = insert_whitespaces(expanded);
38     Some(ExpandedMacro { name: name_ref.text().to_string(), expansion })
39 }
40
41 fn expand_macro_recur(
42     sema: &Semantics<RootDatabase>,
43     macro_call: &ast::MacroCall,
44 ) -> Option<SyntaxNode> {
45     let mut expanded = sema.expand(macro_call)?;
46
47     let children = expanded.descendants().filter_map(ast::MacroCall::cast);
48     let mut rewriter = SyntaxRewriter::default();
49
50     for child in children.into_iter() {
51         if let Some(new_node) = expand_macro_recur(sema, &child) {
52             // Replace the whole node if it is root
53             // `replace_descendants` will not replace the parent node
54             // but `SyntaxNode::descendants include itself
55             if expanded == *child.syntax() {
56                 expanded = new_node;
57             } else {
58                 rewriter.replace(child.syntax(), &new_node)
59             }
60         }
61     }
62
63     let res = rewriter.rewrite(&expanded);
64     Some(res)
65 }
66
67 // FIXME: It would also be cool to share logic here and in the mbe tests,
68 // which are pretty unreadable at the moment.
69 fn insert_whitespaces(syn: SyntaxNode) -> String {
70     let mut res = String::new();
71     let mut token_iter = syn
72         .preorder_with_tokens()
73         .filter_map(|event| {
74             if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
75                 Some(token)
76             } else {
77                 None
78             }
79         })
80         .peekable();
81
82     let mut indent = 0;
83     let mut last: Option<SyntaxKind> = None;
84
85     while let Some(token) = token_iter.next() {
86         let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
87             token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
88         };
89         let is_last =
90             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
91
92         res += &match token.kind() {
93             k if is_text(k) && is_next(|it| !it.is_punct(), true) => token.text().to_string() + " ",
94             L_CURLY if is_next(|it| it != R_CURLY, true) => {
95                 indent += 1;
96                 let leading_space = if is_last(is_text, false) { " " } else { "" };
97                 format!("{}{{\n{}", leading_space, "  ".repeat(indent))
98             }
99             R_CURLY if is_last(|it| it != L_CURLY, true) => {
100                 indent = indent.saturating_sub(1);
101                 format!("\n{}}}", "  ".repeat(indent))
102             }
103             R_CURLY => format!("}}\n{}", "  ".repeat(indent)),
104             T![;] => format!(";\n{}", "  ".repeat(indent)),
105             T![->] => " -> ".to_string(),
106             T![=] => " = ".to_string(),
107             T![=>] => " => ".to_string(),
108             _ => token.text().to_string(),
109         };
110
111         last = Some(token.kind());
112     }
113
114     return res;
115
116     fn is_text(k: SyntaxKind) -> bool {
117         k.is_keyword() || k.is_literal() || k == IDENT
118     }
119 }
120
121 #[cfg(test)]
122 mod tests {
123     use expect_test::{expect, Expect};
124
125     use crate::fixture;
126
127     fn check(ra_fixture: &str, expect: Expect) {
128         let (analysis, pos) = fixture::position(ra_fixture);
129         let expansion = analysis.expand_macro(pos).unwrap().unwrap();
130         let actual = format!("{}\n{}", expansion.name, expansion.expansion);
131         expect.assert_eq(&actual);
132     }
133
134     #[test]
135     fn macro_expand_recursive_expansion() {
136         check(
137             r#"
138 macro_rules! bar {
139     () => { fn  b() {} }
140 }
141 macro_rules! foo {
142     () => { bar!(); }
143 }
144 macro_rules! baz {
145     () => { foo!(); }
146 }
147 f<|>oo!();
148 "#,
149             expect![[r#"
150                 foo
151                 fn b(){}
152             "#]],
153         );
154     }
155
156     #[test]
157     fn macro_expand_multiple_lines() {
158         check(
159             r#"
160 macro_rules! foo {
161     () => {
162         fn some_thing() -> u32 {
163             let a = 0;
164             a + 10
165         }
166     }
167 }
168 f<|>oo!();
169         "#,
170             expect![[r#"
171             foo
172             fn some_thing() -> u32 {
173               let a = 0;
174               a+10
175             }"#]],
176         );
177     }
178
179     #[test]
180     fn macro_expand_match_ast() {
181         check(
182             r#"
183 macro_rules! match_ast {
184     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
185     (match ($node:expr) {
186         $( ast::$ast:ident($it:ident) => $res:block, )*
187         _ => $catch_all:expr $(,)?
188     }) => {{
189         $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
190         { $catch_all }
191     }};
192 }
193
194 fn main() {
195     mat<|>ch_ast! {
196         match container {
197             ast::TraitDef(it) => {},
198             ast::ImplDef(it) => {},
199             _ => { continue },
200         }
201     }
202 }
203 "#,
204             expect![[r#"
205        match_ast
206        {
207          if let Some(it) = ast::TraitDef::cast(container.clone()){}
208          else if let Some(it) = ast::ImplDef::cast(container.clone()){}
209          else {
210            {
211              continue
212            }
213          }
214        }"#]],
215         );
216     }
217
218     #[test]
219     fn macro_expand_match_ast_inside_let_statement() {
220         check(
221             r#"
222 macro_rules! match_ast {
223     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
224     (match ($node:expr) {}) => {{}};
225 }
226
227 fn main() {
228     let p = f(|it| {
229         let res = mat<|>ch_ast! { match c {}};
230         Some(res)
231     })?;
232 }
233 "#,
234             expect![[r#"
235                 match_ast
236                 {}
237             "#]],
238         );
239     }
240
241     #[test]
242     fn macro_expand_inner_macro_fail_to_expand() {
243         check(
244             r#"
245 macro_rules! bar {
246     (BAD) => {};
247 }
248 macro_rules! foo {
249     () => {bar!()};
250 }
251
252 fn main() {
253     let res = fo<|>o!();
254 }
255 "#,
256             expect![[r#"
257                 foo
258             "#]],
259         );
260     }
261
262     #[test]
263     fn macro_expand_with_dollar_crate() {
264         check(
265             r#"
266 #[macro_export]
267 macro_rules! bar {
268     () => {0};
269 }
270 macro_rules! foo {
271     () => {$crate::bar!()};
272 }
273
274 fn main() {
275     let res = fo<|>o!();
276 }
277 "#,
278             expect![[r#"
279                 foo
280                 0 "#]],
281         );
282     }
283 }