]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/syntax_tree.rs
fix: insert whitespaces into assoc items for assist when macro generated
[rust.git] / crates / ide / src / syntax_tree.rs
1 use ide_db::base_db::{FileId, SourceDatabase};
2 use ide_db::RootDatabase;
3 use syntax::{
4     AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
5 };
6
7 // Feature: Show Syntax Tree
8 //
9 // Shows the parse tree of the current file. It exists mostly for debugging
10 // rust-analyzer itself.
11 //
12 // |===
13 // | Editor  | Action Name
14 //
15 // | VS Code | **Rust Analyzer: Show Syntax Tree**
16 // |===
17 // image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[]
18 pub(crate) fn syntax_tree(
19     db: &RootDatabase,
20     file_id: FileId,
21     text_range: Option<TextRange>,
22 ) -> String {
23     let parse = db.parse(file_id);
24     if let Some(text_range) = text_range {
25         let node = match parse.tree().syntax().covering_element(text_range) {
26             NodeOrToken::Node(node) => node,
27             NodeOrToken::Token(token) => {
28                 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
29                     return tree;
30                 }
31                 token.parent().unwrap()
32             }
33         };
34
35         format!("{:#?}", node)
36     } else {
37         format!("{:#?}", parse.tree().syntax())
38     }
39 }
40
41 /// Attempts parsing the selected contents of a string literal
42 /// as rust syntax and returns its syntax tree
43 fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
44     // When the range is inside a string
45     // we'll attempt parsing it as rust syntax
46     // to provide the syntax tree of the contents of the string
47     match token.kind() {
48         STRING => syntax_tree_for_token(token, text_range),
49         _ => None,
50     }
51 }
52
53 fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
54     // Range of the full node
55     let node_range = node.text_range();
56     let text = node.text().to_string();
57
58     // We start at some point inside the node
59     // Either we have selected the whole string
60     // or our selection is inside it
61     let start = text_range.start() - node_range.start();
62
63     // how many characters we have selected
64     let len = text_range.len();
65
66     let node_len = node_range.len();
67
68     let start = start;
69
70     // We want to cap our length
71     let len = len.min(node_len);
72
73     // Ensure our slice is inside the actual string
74     let end =
75         if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
76
77     let text = &text[TextRange::new(start, end)];
78
79     // Remove possible extra string quotes from the start
80     // and the end of the string
81     let text = text
82         .trim_start_matches('r')
83         .trim_start_matches('#')
84         .trim_start_matches('"')
85         .trim_end_matches('#')
86         .trim_end_matches('"')
87         .trim()
88         // Remove custom markers
89         .replace("$0", "");
90
91     let parsed = SourceFile::parse(&text);
92
93     // If the "file" parsed without errors,
94     // return its syntax
95     if parsed.errors().is_empty() {
96         return Some(format!("{:#?}", parsed.tree().syntax()));
97     }
98
99     None
100 }
101
102 #[cfg(test)]
103 mod tests {
104     use expect_test::expect;
105
106     use crate::fixture;
107
108     fn check(ra_fixture: &str, expect: expect_test::Expect) {
109         let (analysis, file_id) = fixture::file(ra_fixture);
110         let syn = analysis.syntax_tree(file_id, None).unwrap();
111         expect.assert_eq(&syn)
112     }
113     fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
114         let (analysis, frange) = fixture::range(ra_fixture);
115         let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
116         expect.assert_eq(&syn)
117     }
118
119     #[test]
120     fn test_syntax_tree_without_range() {
121         // Basic syntax
122         check(
123             r#"fn foo() {}"#,
124             expect![[r#"
125                 SOURCE_FILE@0..11
126                   FN@0..11
127                     FN_KW@0..2 "fn"
128                     WHITESPACE@2..3 " "
129                     NAME@3..6
130                       IDENT@3..6 "foo"
131                     PARAM_LIST@6..8
132                       L_PAREN@6..7 "("
133                       R_PAREN@7..8 ")"
134                     WHITESPACE@8..9 " "
135                     BLOCK_EXPR@9..11
136                       STMT_LIST@9..11
137                         L_CURLY@9..10 "{"
138                         R_CURLY@10..11 "}"
139             "#]],
140         );
141
142         check(
143             r#"
144 fn test() {
145     assert!("
146     fn foo() {
147     }
148     ", "");
149 }"#,
150             expect![[r#"
151                 SOURCE_FILE@0..60
152                   FN@0..60
153                     FN_KW@0..2 "fn"
154                     WHITESPACE@2..3 " "
155                     NAME@3..7
156                       IDENT@3..7 "test"
157                     PARAM_LIST@7..9
158                       L_PAREN@7..8 "("
159                       R_PAREN@8..9 ")"
160                     WHITESPACE@9..10 " "
161                     BLOCK_EXPR@10..60
162                       STMT_LIST@10..60
163                         L_CURLY@10..11 "{"
164                         WHITESPACE@11..16 "\n    "
165                         EXPR_STMT@16..58
166                           MACRO_CALL@16..57
167                             PATH@16..22
168                               PATH_SEGMENT@16..22
169                                 NAME_REF@16..22
170                                   IDENT@16..22 "assert"
171                             BANG@22..23 "!"
172                             TOKEN_TREE@23..57
173                               L_PAREN@23..24 "("
174                               STRING@24..52 "\"\n    fn foo() {\n     ..."
175                               COMMA@52..53 ","
176                               WHITESPACE@53..54 " "
177                               STRING@54..56 "\"\""
178                               R_PAREN@56..57 ")"
179                           SEMICOLON@57..58 ";"
180                         WHITESPACE@58..59 "\n"
181                         R_CURLY@59..60 "}"
182             "#]],
183         )
184     }
185
186     #[test]
187     fn test_syntax_tree_with_range() {
188         check_range(
189             r#"$0fn foo() {}$0"#,
190             expect![[r#"
191                 FN@0..11
192                   FN_KW@0..2 "fn"
193                   WHITESPACE@2..3 " "
194                   NAME@3..6
195                     IDENT@3..6 "foo"
196                   PARAM_LIST@6..8
197                     L_PAREN@6..7 "("
198                     R_PAREN@7..8 ")"
199                   WHITESPACE@8..9 " "
200                   BLOCK_EXPR@9..11
201                     STMT_LIST@9..11
202                       L_CURLY@9..10 "{"
203                       R_CURLY@10..11 "}"
204             "#]],
205         );
206
207         check_range(
208             r#"
209 fn test() {
210     $0assert!("
211     fn foo() {
212     }
213     ", "");$0
214 }"#,
215             expect![[r#"
216                 EXPR_STMT@16..58
217                   MACRO_CALL@16..57
218                     PATH@16..22
219                       PATH_SEGMENT@16..22
220                         NAME_REF@16..22
221                           IDENT@16..22 "assert"
222                     BANG@22..23 "!"
223                     TOKEN_TREE@23..57
224                       L_PAREN@23..24 "("
225                       STRING@24..52 "\"\n    fn foo() {\n     ..."
226                       COMMA@52..53 ","
227                       WHITESPACE@53..54 " "
228                       STRING@54..56 "\"\""
229                       R_PAREN@56..57 ")"
230                   SEMICOLON@57..58 ";"
231             "#]],
232         );
233     }
234
235     #[test]
236     fn test_syntax_tree_inside_string() {
237         check_range(
238             r#"fn test() {
239     assert!("
240 $0fn foo() {
241 }$0
242 fn bar() {
243 }
244     ", "");
245 }"#,
246             expect![[r#"
247                 SOURCE_FILE@0..12
248                   FN@0..12
249                     FN_KW@0..2 "fn"
250                     WHITESPACE@2..3 " "
251                     NAME@3..6
252                       IDENT@3..6 "foo"
253                     PARAM_LIST@6..8
254                       L_PAREN@6..7 "("
255                       R_PAREN@7..8 ")"
256                     WHITESPACE@8..9 " "
257                     BLOCK_EXPR@9..12
258                       STMT_LIST@9..12
259                         L_CURLY@9..10 "{"
260                         WHITESPACE@10..11 "\n"
261                         R_CURLY@11..12 "}"
262             "#]],
263         );
264
265         // With a raw string
266         check_range(
267             r###"fn test() {
268     assert!(r#"
269 $0fn foo() {
270 }$0
271 fn bar() {
272 }
273     "#, "");
274 }"###,
275             expect![[r#"
276                 SOURCE_FILE@0..12
277                   FN@0..12
278                     FN_KW@0..2 "fn"
279                     WHITESPACE@2..3 " "
280                     NAME@3..6
281                       IDENT@3..6 "foo"
282                     PARAM_LIST@6..8
283                       L_PAREN@6..7 "("
284                       R_PAREN@7..8 ")"
285                     WHITESPACE@8..9 " "
286                     BLOCK_EXPR@9..12
287                       STMT_LIST@9..12
288                         L_CURLY@9..10 "{"
289                         WHITESPACE@10..11 "\n"
290                         R_CURLY@11..12 "}"
291             "#]],
292         );
293
294         // With a raw string
295         check_range(
296             r###"fn test() {
297     assert!(r$0#"
298 fn foo() {
299 }
300 fn bar() {
301 }"$0#, "");
302 }"###,
303             expect![[r#"
304                 SOURCE_FILE@0..25
305                   FN@0..12
306                     FN_KW@0..2 "fn"
307                     WHITESPACE@2..3 " "
308                     NAME@3..6
309                       IDENT@3..6 "foo"
310                     PARAM_LIST@6..8
311                       L_PAREN@6..7 "("
312                       R_PAREN@7..8 ")"
313                     WHITESPACE@8..9 " "
314                     BLOCK_EXPR@9..12
315                       STMT_LIST@9..12
316                         L_CURLY@9..10 "{"
317                         WHITESPACE@10..11 "\n"
318                         R_CURLY@11..12 "}"
319                   WHITESPACE@12..13 "\n"
320                   FN@13..25
321                     FN_KW@13..15 "fn"
322                     WHITESPACE@15..16 " "
323                     NAME@16..19
324                       IDENT@16..19 "bar"
325                     PARAM_LIST@19..21
326                       L_PAREN@19..20 "("
327                       R_PAREN@20..21 ")"
328                     WHITESPACE@21..22 " "
329                     BLOCK_EXPR@22..25
330                       STMT_LIST@22..25
331                         L_CURLY@22..23 "{"
332                         WHITESPACE@23..24 "\n"
333                         R_CURLY@24..25 "}"
334             "#]],
335         );
336     }
337 }