aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/syntax_tree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/syntax_tree.rs')
-rw-r--r--crates/ide/src/syntax_tree.rs359
1 files changed, 359 insertions, 0 deletions
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
new file mode 100644
index 000000000..f80044959
--- /dev/null
+++ b/crates/ide/src/syntax_tree.rs
@@ -0,0 +1,359 @@
1use base_db::{FileId, SourceDatabase};
2use ide_db::RootDatabase;
3use 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// |===
19pub(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
44fn 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
54fn 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)]
104mod 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[email protected]
119 [email protected]
120 [email protected] "fn"
121 [email protected] " "
122 [email protected]
123 [email protected] "foo"
124 [email protected]
125 [email protected] "("
126 [email protected] ")"
127 [email protected] " "
128 [email protected]
129 [email protected] "{"
130 [email protected] "}"
131"#
132 .trim()
133 );
134
135 let (analysis, file_id) = single_file(
136 r#"
137fn 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[email protected]
151 [email protected]
152 [email protected] "fn"
153 [email protected] " "
154 [email protected]
155 [email protected] "test"
156 [email protected]
157 [email protected] "("
158 [email protected] ")"
159 [email protected] " "
160 [email protected]
161 [email protected] "{"
162 [email protected] "\n "
163 [email protected]
164 [email protected]
165 [email protected]
166 [email protected]
167 [email protected]
168 [email protected] "assert"
169 [email protected] "!"
170 [email protected]
171 [email protected] "("
172 [email protected] "\"\n fn foo() {\n ..."
173 [email protected] ","
174 [email protected] " "
175 [email protected] "\"\""
176 [email protected] ")"
177 [email protected] ";"
178 [email protected] "\n"
179 [email protected] "}"
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[email protected]
194 [email protected] "fn"
195 [email protected] " "
196 [email protected]
197 [email protected] "foo"
198 [email protected]
199 [email protected] "("
200 [email protected] ")"
201 [email protected] " "
202 [email protected]
203 [email protected] "{"
204 [email protected] "}"
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[email protected]
224 [email protected]
225 [email protected]
226 [email protected]
227 [email protected]
228 [email protected] "assert"
229 [email protected] "!"
230 [email protected]
231 [email protected] "("
232 [email protected] "\"\n fn foo() {\n ..."
233 [email protected] ","
234 [email protected] " "
235 [email protected] "\"\""
236 [email protected] ")"
237 [email protected] ";"
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}<|>
250fn 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[email protected]
261 [email protected]
262 [email protected] "fn"
263 [email protected] " "
264 [email protected]
265 [email protected] "foo"
266 [email protected]
267 [email protected] "("
268 [email protected] ")"
269 [email protected] " "
270 [email protected]
271 [email protected] "{"
272 [email protected] "\n"
273 [email protected] "}"
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}<|>
284fn 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[email protected]
295 [email protected]
296 [email protected] "fn"
297 [email protected] " "
298 [email protected]
299 [email protected] "foo"
300 [email protected]
301 [email protected] "("
302 [email protected] ")"
303 [email protected] " "
304 [email protected]
305 [email protected] "{"
306 [email protected] "\n"
307 [email protected] "}"
308"#
309 .trim()
310 );
311
312 // With a raw string
313 let (analysis, range) = analysis_and_range(
314 r###"fn test() {
315 assert!(r<|>#"
316fn foo() {
317}
318fn 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[email protected]
328 [email protected]
329 [email protected] "fn"
330 [email protected] " "
331 [email protected]
332 [email protected] "foo"
333 [email protected]
334 [email protected] "("
335 [email protected] ")"
336 [email protected] " "
337 [email protected]
338 [email protected] "{"
339 [email protected] "\n"
340 [email protected] "}"
341 [email protected] "\n"
342 [email protected]
343 [email protected] "fn"
344 [email protected] " "
345 [email protected]
346 [email protected] "bar"
347 [email protected]
348 [email protected] "("
349 [email protected] ")"
350 [email protected] " "
351 [email protected]
352 [email protected] "{"
353 [email protected] "\n"
354 [email protected] "}"
355"#
356 .trim()
357 );
358 }
359}