1 use ide_db::base_db::{FileId, SourceDatabase};
2 use ide_db::RootDatabase;
4 AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
7 // Feature: Show Syntax Tree
9 // Shows the parse tree of the current file. It exists mostly for debugging
10 // rust-analyzer itself.
13 // | Editor | Action Name
15 // | VS Code | **Rust Analyzer: Show Syntax Tree**
17 // image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[]
18 pub(crate) fn syntax_tree(
21 text_range: Option<TextRange>,
23 let parse = db.parse(file_id);
24 if let Some(text_range) = text_range {
25 let node = match parse.tree().syntax().covering_element(text_range) {
26 NodeOrToken::Node(node) => node,
27 NodeOrToken::Token(token) => {
28 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
31 token.parent().unwrap()
35 format!("{:#?}", node)
37 format!("{:#?}", parse.tree().syntax())
41 /// Attempts parsing the selected contents of a string literal
42 /// as rust syntax and returns its syntax tree
43 fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
44 // When the range is inside a string
45 // we'll attempt parsing it as rust syntax
46 // to provide the syntax tree of the contents of the string
48 STRING => syntax_tree_for_token(token, text_range),
53 fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
54 // Range of the full node
55 let node_range = node.text_range();
56 let text = node.text().to_string();
58 // We start at some point inside the node
59 // Either we have selected the whole string
60 // or our selection is inside it
61 let start = text_range.start() - node_range.start();
63 // how many characters we have selected
64 let len = text_range.len();
66 let node_len = node_range.len();
70 // We want to cap our length
71 let len = len.min(node_len);
73 // Ensure our slice is inside the actual string
75 if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
77 let text = &text[TextRange::new(start, end)];
79 // Remove possible extra string quotes from the start
80 // and the end of the string
82 .trim_start_matches('r')
83 .trim_start_matches('#')
84 .trim_start_matches('"')
85 .trim_end_matches('#')
86 .trim_end_matches('"')
88 // Remove custom markers
91 let parsed = SourceFile::parse(&text);
93 // If the "file" parsed without errors,
95 if parsed.errors().is_empty() {
96 return Some(format!("{:#?}", parsed.tree().syntax()));
104 use expect_test::expect;
108 fn check(ra_fixture: &str, expect: expect_test::Expect) {
109 let (analysis, file_id) = fixture::file(ra_fixture);
110 let syn = analysis.syntax_tree(file_id, None).unwrap();
111 expect.assert_eq(&syn)
113 fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
114 let (analysis, frange) = fixture::range(ra_fixture);
115 let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
116 expect.assert_eq(&syn)
120 fn test_syntax_tree_without_range() {
164 WHITESPACE@11..16 "\n "
170 IDENT@16..22 "assert"
174 STRING@24..52 "\"\n fn foo() {\n ..."
176 WHITESPACE@53..54 " "
180 WHITESPACE@58..59 "\n"
187 fn test_syntax_tree_with_range() {
189 r#"$0fn foo() {}$0"#,
221 IDENT@16..22 "assert"
225 STRING@24..52 "\"\n fn foo() {\n ..."
227 WHITESPACE@53..54 " "
236 fn test_syntax_tree_inside_string() {
260 WHITESPACE@10..11 "\n"
289 WHITESPACE@10..11 "\n"
317 WHITESPACE@10..11 "\n"
319 WHITESPACE@12..13 "\n"
322 WHITESPACE@15..16 " "
328 WHITESPACE@21..22 " "
332 WHITESPACE@23..24 "\n"