Skip to main content

ra_ap_rust_analyzer/cli/
parse.rs

1//! Read Rust code on stdin, print syntax tree on stdout.
2use ide::Edition;
3use ide_db::line_index::LineIndex;
4use serde::Serialize;
5use syntax::{AstNode, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken};
6
7use crate::cli::{flags, read_stdin};
8
9#[derive(Serialize)]
10struct JsonNode {
11    kind: String,
12    #[serde(rename = "type")]
13    node_type: &'static str,
14    start: [u32; 3],
15    end: [u32; 3],
16    #[serde(skip_serializing_if = "Option::is_none")]
17    text: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    children: Option<Vec<JsonNode>>,
20}
21
22fn pos(line_index: &LineIndex, offset: syntax::TextSize) -> [u32; 3] {
23    let offset_u32 = u32::from(offset);
24    let line_col = line_index.line_col(offset);
25    [offset_u32, line_col.line, line_col.col]
26}
27
28impl flags::Parse {
29    pub fn run(self) -> anyhow::Result<()> {
30        let _p = tracing::info_span!("flags::Parse::run").entered();
31        let text = read_stdin()?;
32        let line_index = LineIndex::new(&text);
33        let file = SourceFile::parse(&text, Edition::CURRENT).tree();
34
35        if !self.no_dump {
36            if self.json {
37                let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index);
38                println!("{}", serde_json::to_string(&json_tree)?);
39            } else {
40                println!("{:#?}", file.syntax());
41            }
42        }
43
44        std::mem::forget(file);
45        Ok(())
46    }
47}
48
49fn node_to_json(node: NodeOrToken<SyntaxNode, SyntaxToken>, line_index: &LineIndex) -> JsonNode {
50    let range = node.text_range();
51    let kind = format!("{:?}", node.kind());
52
53    match node {
54        NodeOrToken::Node(n) => {
55            let children: Vec<_> =
56                n.children_with_tokens().map(|it| node_to_json(it, line_index)).collect();
57            JsonNode {
58                kind,
59                node_type: "Node",
60                start: pos(line_index, range.start()),
61                end: pos(line_index, range.end()),
62                text: None,
63                children: Some(children),
64            }
65        }
66        NodeOrToken::Token(t) => JsonNode {
67            kind,
68            node_type: "Token",
69            start: pos(line_index, range.start()),
70            end: pos(line_index, range.end()),
71            text: Some(t.text().to_owned()),
72            children: None,
73        },
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::cli::flags;
81
82    #[test]
83    fn test_parse_json_output() {
84        let text = "fn main() {}".to_owned();
85        let flags = flags::Parse { json: true, no_dump: false };
86        let line_index = LineIndex::new(&text);
87
88        let file = SourceFile::parse(&text, Edition::CURRENT).tree();
89
90        let output = if flags.json {
91            let json_tree = node_to_json(NodeOrToken::Node(file.syntax().clone()), &line_index);
92            serde_json::to_string(&json_tree).unwrap()
93        } else {
94            format!("{:#?}", file.syntax())
95        };
96
97        assert!(output.contains(r#""kind":"SOURCE_FILE""#));
98        assert!(output.contains(r#""text":"main""#));
99        assert!(output.contains(r#""start":[0,0,0]"#));
100    }
101}