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