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 pub(crate) fn syntax_tree(
20 text_range: Option<TextRange>,
22 let parse = db.parse(file_id);
23 if let Some(text_range) = text_range {
24 let node = match parse.tree().syntax().covering_element(text_range) {
25 NodeOrToken::Node(node) => node,
26 NodeOrToken::Token(token) => {
27 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
34 format!("{:#?}", node)
36 format!("{:#?}", parse.tree().syntax())
40 /// Attempts parsing the selected contents of a string literal
41 /// as rust syntax and returns its syntax tree
42 fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
43 // When the range is inside a string
44 // we'll attempt parsing it as rust syntax
45 // to provide the syntax tree of the contents of the string
47 STRING => syntax_tree_for_token(token, text_range),
52 fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
53 // Range of the full node
54 let node_range = node.text_range();
55 let text = node.text().to_string();
57 // We start at some point inside the node
58 // Either we have selected the whole string
59 // or our selection is inside it
60 let start = text_range.start() - node_range.start();
62 // how many characters we have selected
63 let len = text_range.len();
65 let node_len = node_range.len();
69 // We want to cap our length
70 let len = len.min(node_len);
72 // Ensure our slice is inside the actual string
74 if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
76 let text = &text[TextRange::new(start, end)];
78 // Remove possible extra string quotes from the start
79 // and the end of the string
81 .trim_start_matches('r')
82 .trim_start_matches('#')
83 .trim_start_matches('"')
84 .trim_end_matches('#')
85 .trim_end_matches('"')
87 // Remove custom markers
90 let parsed = SourceFile::parse(&text);
92 // If the "file" parsed without errors,
94 if parsed.errors().is_empty() {
95 return Some(format!("{:#?}", parsed.tree().syntax()));
103 use expect_test::expect;
107 fn check(ra_fixture: &str, expect: expect_test::Expect) {
108 let (analysis, file_id) = fixture::file(ra_fixture);
109 let syn = analysis.syntax_tree(file_id, None).unwrap();
110 expect.assert_eq(&syn)
112 fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
113 let (analysis, frange) = fixture::range(ra_fixture);
114 let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
115 expect.assert_eq(&syn)
119 fn test_syntax_tree_without_range() {
161 WHITESPACE@11..16 "\n "
167 IDENT@16..22 "assert"
171 STRING@24..52 "\"\n fn foo() {\n ..."
173 WHITESPACE@53..54 " "
177 WHITESPACE@58..59 "\n"
184 fn test_syntax_tree_with_range() {
186 r#"$0fn foo() {}$0"#,
217 IDENT@16..22 "assert"
221 STRING@24..52 "\"\n fn foo() {\n ..."
223 WHITESPACE@53..54 " "
232 fn test_syntax_tree_inside_string() {
255 WHITESPACE@10..11 "\n"
283 WHITESPACE@10..11 "\n"
310 WHITESPACE@10..11 "\n"
312 WHITESPACE@12..13 "\n"
315 WHITESPACE@15..16 " "
321 WHITESPACE@21..22 " "
324 WHITESPACE@23..24 "\n"