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