diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-11-27 18:45:05 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2019-11-27 18:45:05 +0000 |
commit | 360f6b7bb32d6280ed787075c4a54f4e5b46fcb6 (patch) | |
tree | 4c059427819ef442c785125f48fe83f81f6d667a /crates/ra_ide/src/syntax_tree.rs | |
parent | 4946169a96f3d442f463724af481fdb760381ccb (diff) | |
parent | 27b362b05910c81fd5b28f6cd5d2c075311032f9 (diff) |
Merge #2430
2430: rename ra_ide_api -> ra_ide r=matklad a=matklad
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_ide/src/syntax_tree.rs')
-rw-r--r-- | crates/ra_ide/src/syntax_tree.rs | 359 |
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 | |||
3 | use crate::db::RootDatabase; | ||
4 | use ra_db::SourceDatabase; | ||
5 | use ra_syntax::{ | ||
6 | algo, AstNode, NodeOrToken, SourceFile, | ||
7 | SyntaxKind::{RAW_STRING, STRING}, | ||
8 | SyntaxToken, TextRange, | ||
9 | }; | ||
10 | |||
11 | pub use ra_db::FileId; | ||
12 | |||
13 | pub(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 | ||
38 | fn 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 | |||
48 | fn 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)] | ||
97 | mod 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#" | ||
111 | SOURCE_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#" | ||
131 | fn 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#" | ||
144 | SOURCE_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#" | ||
188 | FN_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#" | ||
219 | EXPR_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 | }<|> | ||
246 | fn 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#" | ||
256 | SOURCE_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 | }<|> | ||
281 | fn 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#" | ||
291 | SOURCE_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<|>#" | ||
314 | fn foo() { | ||
315 | } | ||
316 | fn 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#" | ||
325 | SOURCE_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 | } | ||