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