]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
Rollup merge of #99614 - RalfJung:transmute-is-not-memcpy, r=thomcc
[rust.git] / src / tools / rust-analyzer / 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_EXPR@16..57
167                             MACRO_CALL@16..57
168                               PATH@16..22
169                                 PATH_SEGMENT@16..22
170                                   NAME_REF@16..22
171                                     IDENT@16..22 "assert"
172                               BANG@22..23 "!"
173                               TOKEN_TREE@23..57
174                                 L_PAREN@23..24 "("
175                                 STRING@24..52 "\"\n    fn foo() {\n     ..."
176                                 COMMA@52..53 ","
177                                 WHITESPACE@53..54 " "
178                                 STRING@54..56 "\"\""
179                                 R_PAREN@56..57 ")"
180                           SEMICOLON@57..58 ";"
181                         WHITESPACE@58..59 "\n"
182                         R_CURLY@59..60 "}"
183             "#]],
184         )
185     }
186
187     #[test]
188     fn test_syntax_tree_with_range() {
189         check_range(
190             r#"$0fn foo() {}$0"#,
191             expect![[r#"
192                 FN@0..11
193                   FN_KW@0..2 "fn"
194                   WHITESPACE@2..3 " "
195                   NAME@3..6
196                     IDENT@3..6 "foo"
197                   PARAM_LIST@6..8
198                     L_PAREN@6..7 "("
199                     R_PAREN@7..8 ")"
200                   WHITESPACE@8..9 " "
201                   BLOCK_EXPR@9..11
202                     STMT_LIST@9..11
203                       L_CURLY@9..10 "{"
204                       R_CURLY@10..11 "}"
205             "#]],
206         );
207
208         check_range(
209             r#"
210 fn test() {
211     $0assert!("
212     fn foo() {
213     }
214     ", "");$0
215 }"#,
216             expect![[r#"
217                 EXPR_STMT@16..58
218                   MACRO_EXPR@16..57
219                     MACRO_CALL@16..57
220                       PATH@16..22
221                         PATH_SEGMENT@16..22
222                           NAME_REF@16..22
223                             IDENT@16..22 "assert"
224                       BANG@22..23 "!"
225                       TOKEN_TREE@23..57
226                         L_PAREN@23..24 "("
227                         STRING@24..52 "\"\n    fn foo() {\n     ..."
228                         COMMA@52..53 ","
229                         WHITESPACE@53..54 " "
230                         STRING@54..56 "\"\""
231                         R_PAREN@56..57 ")"
232                   SEMICOLON@57..58 ";"
233             "#]],
234         );
235     }
236
237     #[test]
238     fn test_syntax_tree_inside_string() {
239         check_range(
240             r#"fn test() {
241     assert!("
242 $0fn foo() {
243 }$0
244 fn bar() {
245 }
246     ", "");
247 }"#,
248             expect![[r#"
249                 SOURCE_FILE@0..12
250                   FN@0..12
251                     FN_KW@0..2 "fn"
252                     WHITESPACE@2..3 " "
253                     NAME@3..6
254                       IDENT@3..6 "foo"
255                     PARAM_LIST@6..8
256                       L_PAREN@6..7 "("
257                       R_PAREN@7..8 ")"
258                     WHITESPACE@8..9 " "
259                     BLOCK_EXPR@9..12
260                       STMT_LIST@9..12
261                         L_CURLY@9..10 "{"
262                         WHITESPACE@10..11 "\n"
263                         R_CURLY@11..12 "}"
264             "#]],
265         );
266
267         // With a raw string
268         check_range(
269             r###"fn test() {
270     assert!(r#"
271 $0fn foo() {
272 }$0
273 fn bar() {
274 }
275     "#, "");
276 }"###,
277             expect![[r#"
278                 SOURCE_FILE@0..12
279                   FN@0..12
280                     FN_KW@0..2 "fn"
281                     WHITESPACE@2..3 " "
282                     NAME@3..6
283                       IDENT@3..6 "foo"
284                     PARAM_LIST@6..8
285                       L_PAREN@6..7 "("
286                       R_PAREN@7..8 ")"
287                     WHITESPACE@8..9 " "
288                     BLOCK_EXPR@9..12
289                       STMT_LIST@9..12
290                         L_CURLY@9..10 "{"
291                         WHITESPACE@10..11 "\n"
292                         R_CURLY@11..12 "}"
293             "#]],
294         );
295
296         // With a raw string
297         check_range(
298             r###"fn test() {
299     assert!(r$0#"
300 fn foo() {
301 }
302 fn bar() {
303 }"$0#, "");
304 }"###,
305             expect![[r#"
306                 SOURCE_FILE@0..25
307                   FN@0..12
308                     FN_KW@0..2 "fn"
309                     WHITESPACE@2..3 " "
310                     NAME@3..6
311                       IDENT@3..6 "foo"
312                     PARAM_LIST@6..8
313                       L_PAREN@6..7 "("
314                       R_PAREN@7..8 ")"
315                     WHITESPACE@8..9 " "
316                     BLOCK_EXPR@9..12
317                       STMT_LIST@9..12
318                         L_CURLY@9..10 "{"
319                         WHITESPACE@10..11 "\n"
320                         R_CURLY@11..12 "}"
321                   WHITESPACE@12..13 "\n"
322                   FN@13..25
323                     FN_KW@13..15 "fn"
324                     WHITESPACE@15..16 " "
325                     NAME@16..19
326                       IDENT@16..19 "bar"
327                     PARAM_LIST@19..21
328                       L_PAREN@19..20 "("
329                       R_PAREN@20..21 ")"
330                     WHITESPACE@21..22 " "
331                     BLOCK_EXPR@22..25
332                       STMT_LIST@22..25
333                         L_CURLY@22..23 "{"
334                         WHITESPACE@23..24 "\n"
335                         R_CURLY@24..25 "}"
336             "#]],
337         );
338     }
339 }